Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Azure;

namespace Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks
{
/// <summary>
/// A helper for providing data with a health check result.
/// </summary>
internal partial class HealthCheckData
{
// exposed to the HealthCheckResult through IReadOnlyDictionary.
private readonly Dictionary<string, object> _data = [];

public string Source
{
get => GetOrDefault<string>();
set => Set(value);
}

public string ConfigurationSection
{
get => GetOrDefault<string>();
set => Set(value);
}

public int StatusCode
{
get => GetOrDefault<int>();
set => Set(value);
}

public string ErrorCode
{
get => GetOrDefault<string>();
set => Set(value);
}

public void SetExceptionDetails(Exception ex)
{
ArgumentNullException.ThrowIfNull(ex);
if (ex is AggregateException aggregate)
{
// Azure SDK will retry a few times in some cases, leading to multiple inner exceptions.
// We only care about the last one.
ex = aggregate.InnerExceptions.Last();
}

if (ex is TimeoutException)
{
ErrorCode = "Timeout";
}
else if (ex is OperationCanceledException)
{
ErrorCode = "OperationCanceled";
}
else if (ex is RequestFailedException rfe)
{
StatusCode = rfe.Status;
ErrorCode = rfe.ErrorCode;
}
}

private void Set<T>(T value, [CallerMemberName] string key = null)
{
_data[key] = value;
}

private T GetOrDefault<T>([CallerMemberName] string key = null, T defaultValue = default)
{
if (_data.TryGetValue(key, out var value) && value is T typedValue)
{
return typedValue;
}

return defaultValue;
}
}

// Partial class down here to separate IReadOnlyDictionary implementation details.
internal partial class HealthCheckData : IReadOnlyDictionary<string, object>
{
IEnumerable<string> IReadOnlyDictionary<string, object>.Keys
=> _data.Keys;

IEnumerable<object> IReadOnlyDictionary<string, object>.Values
=> _data.Values;

int IReadOnlyCollection<KeyValuePair<string, object>>.Count
=> _data.Count;

object IReadOnlyDictionary<string, object>.this[string key]
=> _data[key];

bool IReadOnlyDictionary<string, object>.ContainsKey(string key)
=> _data.ContainsKey(key);

IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
=> _data.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator()
=> _data.GetEnumerator();

bool IReadOnlyDictionary<string, object>.TryGetValue(string key, out object value)
=> _data.TryGetValue(key, out value);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;

Expand All @@ -27,6 +28,7 @@ public static IHealthChecksBuilder AddWebJobsScriptHealthChecks(this IHealthChec
builder
.AddWebHostHealthCheck()
.AddScriptHostHealthCheck()
.AddWebJobsStorageHealthCheck()
.AddTelemetryPublisher(HealthCheckTags.Liveness, HealthCheckTags.Readiness)
.UseDynamicHealthCheckService();
return builder;
Expand Down Expand Up @@ -126,6 +128,19 @@ public static IHealthChecksBuilder AddScriptHostHealthCheck(this IHealthChecksBu
return builder;
}

public static IHealthChecksBuilder AddWebJobsStorageHealthCheck(this IHealthChecksBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder);

// Ensure singleton as this health check refreshes in the background.
builder.Services.TryAddSingleton<WebJobsStorageHealthCheck>();
builder.AddCheck<WebJobsStorageHealthCheck>(
HealthCheckNames.WebJobsStorage,
tags: [HealthCheckTags.Configuration, HealthCheckTags.WebJobsStorage],
timeout: TimeSpan.FromSeconds(10));
return builder;
}

/// <summary>
/// Filters a health report to include only specified entries.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,10 @@ internal static class HealthCheckNames
/// The 'azure.functions.script_host.lifecycle' check monitors the lifecycle of the script host.
/// </summary>
public const string ScriptHostLifeCycle = Prefix + "script_host.lifecycle";

/// <summary>
/// The 'azure.functions.web_jobs.storage' check monitors connectivity to the WebJobs storage account.
/// </summary>
public const string WebJobsStorage = Prefix + "web_jobs.storage";
}
}
16 changes: 11 additions & 5 deletions src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckTags.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

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

/// <summary>
/// The 'azure.functions.liveness' tag is used for liveness checks in the Azure Functions host.
/// </summary>
/// <remarks>
/// Liveness checks are used to determine if the host is alive and responsive.
/// </remarks>
public const string Liveness = Prefix + "liveness";
public const string Liveness = FuncPrefix + "liveness";

/// <summary>
/// The 'azure.functions.readiness' tag is used for readiness checks in the Azure Functions host.
/// </summary>
/// <remarks>
/// Readiness checks are used to determine if the host is ready to process requests.
/// </remarks>
public const string Readiness = Prefix + "readiness";
public const string Readiness = FuncPrefix + "readiness";

/// <summary>
/// The 'azure.functions.configuration' tag is used for configuration-related health checks in the Azure Functions host.
/// </summary>
/// <remarks>
/// These are typically customer configuration related, such as configuring AzureWebJobsStorage access.
/// </remarks>
public const string Configuration = Prefix + "configuration";
public const string Configuration = FuncPrefix + "configuration";

/// <summary>
/// The "azure.web_jobs.storage" tag is used for health checks related to the WebJobs storage account.
/// </summary>
public const string WebJobsStorage = WebJobsPrefix + "storage";
}
}
Loading
Loading