Skip to content

Commit efafd99

Browse files
committed
Add AzureWebJobsStorage health check
1 parent 1d92579 commit efafd99

File tree

8 files changed

+559
-10
lines changed

8 files changed

+559
-10
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Runtime.CompilerServices;
9+
using Azure;
10+
11+
namespace Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks
12+
{
13+
/// <summary>
14+
/// A helper for providing data with a health check result.
15+
/// </summary>
16+
internal partial class HealthCheckData
17+
{
18+
// exposed to the HealthCheckResult through IReadOnlyDictionary.
19+
private readonly Dictionary<string, object> _data = [];
20+
21+
public string Source
22+
{
23+
get => GetOrDefault<string>();
24+
set => Set(value);
25+
}
26+
27+
public string ConfigurationSection
28+
{
29+
get => GetOrDefault<string>();
30+
set => Set(value);
31+
}
32+
33+
public int StatusCode
34+
{
35+
get => GetOrDefault<int>();
36+
set => Set(value);
37+
}
38+
39+
public string ErrorCode
40+
{
41+
get => GetOrDefault<string>();
42+
set => Set(value);
43+
}
44+
45+
public void SetExceptionDetails(Exception ex)
46+
{
47+
ArgumentNullException.ThrowIfNull(ex);
48+
if (ex is AggregateException aggregate)
49+
{
50+
// Azure SDK will retry a few times in some cases, leading to multiple inner exceptions.
51+
// We only care about the last one.
52+
ex = aggregate.InnerExceptions.Last();
53+
}
54+
55+
if (ex is TimeoutException)
56+
{
57+
ErrorCode = "Timeout";
58+
}
59+
else if (ex is OperationCanceledException)
60+
{
61+
ErrorCode = "OperationCanceled";
62+
}
63+
else if (ex is RequestFailedException rfe)
64+
{
65+
StatusCode = rfe.Status;
66+
ErrorCode = rfe.ErrorCode;
67+
}
68+
}
69+
70+
private void Set<T>(T value, [CallerMemberName] string key = null)
71+
{
72+
_data[key] = value;
73+
}
74+
75+
private T GetOrDefault<T>([CallerMemberName] string key = null, T defaultValue = default)
76+
{
77+
if (_data.TryGetValue(key, out var value) && value is T typedValue)
78+
{
79+
return typedValue;
80+
}
81+
82+
return defaultValue;
83+
}
84+
}
85+
86+
// Partial class down here to separate IReadOnlyDictionary implementation details.
87+
internal partial class HealthCheckData : IReadOnlyDictionary<string, object>
88+
{
89+
IEnumerable<string> IReadOnlyDictionary<string, object>.Keys
90+
=> _data.Keys;
91+
92+
IEnumerable<object> IReadOnlyDictionary<string, object>.Values
93+
=> _data.Values;
94+
95+
int IReadOnlyCollection<KeyValuePair<string, object>>.Count
96+
=> _data.Count;
97+
98+
object IReadOnlyDictionary<string, object>.this[string key]
99+
=> _data[key];
100+
101+
bool IReadOnlyDictionary<string, object>.ContainsKey(string key)
102+
=> _data.ContainsKey(key);
103+
104+
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
105+
=> _data.GetEnumerator();
106+
107+
IEnumerator IEnumerable.GetEnumerator()
108+
=> _data.GetEnumerator();
109+
110+
bool IReadOnlyDictionary<string, object>.TryGetValue(string key, out object value)
111+
=> _data.TryGetValue(key, out value);
112+
}
113+
}

src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckExtensions.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
77
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.DependencyInjection.Extensions;
89
using Microsoft.Extensions.Diagnostics.HealthChecks;
910
using Microsoft.Extensions.Logging;
1011

@@ -27,6 +28,7 @@ public static IHealthChecksBuilder AddWebJobsScriptHealthChecks(this IHealthChec
2728
builder
2829
.AddWebHostHealthCheck()
2930
.AddScriptHostHealthCheck()
31+
.AddWebJobsStorageHealthCheck()
3032
.AddTelemetryPublisher(HealthCheckTags.Liveness, HealthCheckTags.Readiness)
3133
.UseDynamicHealthCheckService();
3234
return builder;
@@ -126,6 +128,19 @@ public static IHealthChecksBuilder AddScriptHostHealthCheck(this IHealthChecksBu
126128
return builder;
127129
}
128130

131+
public static IHealthChecksBuilder AddWebJobsStorageHealthCheck(this IHealthChecksBuilder builder)
132+
{
133+
ArgumentNullException.ThrowIfNull(builder);
134+
135+
// Ensure singleton as this health check refreshes in the background.
136+
builder.Services.TryAddSingleton<WebJobsStorageHealthCheck>();
137+
builder.AddCheck<WebJobsStorageHealthCheck>(
138+
HealthCheckNames.WebJobsStorage,
139+
tags: [HealthCheckTags.Configuration, HealthCheckTags.WebJobsStorage],
140+
timeout: TimeSpan.FromSeconds(10));
141+
return builder;
142+
}
143+
129144
/// <summary>
130145
/// Filters a health report to include only specified entries.
131146
/// </summary>

src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckNames.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,10 @@ internal static class HealthCheckNames
1919
/// The 'azure.functions.script_host.lifecycle' check monitors the lifecycle of the script host.
2020
/// </summary>
2121
public const string ScriptHostLifeCycle = Prefix + "script_host.lifecycle";
22+
23+
/// <summary>
24+
/// The 'azure.functions.web_jobs.storage' check monitors connectivity to the WebJobs storage account.
25+
/// </summary>
26+
public const string WebJobsStorage = Prefix + "web_jobs.storage";
2227
}
2328
}
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

44
namespace Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks
@@ -8,30 +8,36 @@ namespace Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks
88
/// </summary>
99
internal static class HealthCheckTags
1010
{
11-
private const string Prefix = "azure.functions.";
11+
private const string FuncPrefix = "azure.functions.";
12+
private const string WebJobsPrefix = "azure.web_jobs.";
1213

1314
/// <summary>
1415
/// The 'azure.functions.liveness' tag is used for liveness checks in the Azure Functions host.
1516
/// </summary>
1617
/// <remarks>
1718
/// Liveness checks are used to determine if the host is alive and responsive.
1819
/// </remarks>
19-
public const string Liveness = Prefix + "liveness";
20+
public const string Liveness = FuncPrefix + "liveness";
2021

2122
/// <summary>
2223
/// The 'azure.functions.readiness' tag is used for readiness checks in the Azure Functions host.
2324
/// </summary>
2425
/// <remarks>
2526
/// Readiness checks are used to determine if the host is ready to process requests.
2627
/// </remarks>
27-
public const string Readiness = Prefix + "readiness";
28+
public const string Readiness = FuncPrefix + "readiness";
2829

2930
/// <summary>
3031
/// The 'azure.functions.configuration' tag is used for configuration-related health checks in the Azure Functions host.
3132
/// </summary>
3233
/// <remarks>
3334
/// These are typically customer configuration related, such as configuring AzureWebJobsStorage access.
3435
/// </remarks>
35-
public const string Configuration = Prefix + "configuration";
36+
public const string Configuration = FuncPrefix + "configuration";
37+
38+
/// <summary>
39+
/// The "azure.web_jobs.storage" tag is used for health checks related to the WebJobs storage account.
40+
/// </summary>
41+
public const string WebJobsStorage = WebJobsPrefix + "storage";
3642
}
3743
}

0 commit comments

Comments
 (0)