diff --git a/src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs b/src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs
index 0d5eb6d79e..fd9296cd61 100644
--- a/src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs
+++ b/src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs
@@ -13,6 +13,7 @@
using Microsoft.Azure.WebJobs.Script.Config;
using Microsoft.Azure.WebJobs.Script.Configuration;
using Microsoft.Azure.WebJobs.Script.Diagnostics;
+using Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks;
using Microsoft.Azure.WebJobs.Script.Grpc;
using Microsoft.Azure.WebJobs.Script.Metrics;
using Microsoft.Azure.WebJobs.Script.Middleware;
@@ -236,6 +237,9 @@ public static void AddWebJobsScriptHost(this IServiceCollection services, IConfi
// Add AzureBlobStorageProvider to WebHost (also needed for ScriptHost) and AzureTableStorageProvider
services.AddAzureStorageProviders();
+
+ // Add health checks
+ services.AddHealthChecks().AddWebJobsScriptHealthChecks();
}
internal static void AddHostingConfigOptions(this IServiceCollection services, IConfiguration configuration)
diff --git a/src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckExtensions.cs b/src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckExtensions.cs
new file mode 100644
index 0000000000..36bcd4c34d
--- /dev/null
+++ b/src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckExtensions.cs
@@ -0,0 +1,59 @@
+// 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.Diagnostics.HealthChecks;
+
+namespace Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks
+{
+ ///
+ /// Health check related extension methods.
+ ///
+ internal static class HealthCheckExtensions
+ {
+ ///
+ /// Registers all health check services required for the functions host. Should be called
+ /// on the WebHost.
+ ///
+ /// The builder to register health checks with.
+ /// The original builder, for call chaining.
+ public static IHealthChecksBuilder AddWebJobsScriptHealthChecks(this IHealthChecksBuilder builder)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ builder
+ .AddWebHostHealthCheck()
+ .AddScriptHostHealthCheck();
+
+ return builder;
+ }
+
+ ///
+ /// Adds a health check for the web host lifecycle.
+ ///
+ /// The builder to register health checks with.
+ /// The original builder, for call chaining.
+ public static IHealthChecksBuilder AddWebHostHealthCheck(this IHealthChecksBuilder builder)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ builder.AddCheck(
+ HealthCheckNames.WebHostLifeCycle, tags: [HealthCheckTags.Liveness]);
+ return builder;
+ }
+
+ ///
+ /// Adds a health check for the script host lifecycle.
+ ///
+ /// The builder to register health checks with.
+ /// The original builder, for call chaining.
+ public static IHealthChecksBuilder AddScriptHostHealthCheck(this IHealthChecksBuilder builder)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ builder.AddCheck(
+ HealthCheckNames.ScriptHostLifeCycle, tags: [HealthCheckTags.Readiness]);
+ return builder;
+ }
+ }
+}
diff --git a/src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckNames.cs b/src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckNames.cs
new file mode 100644
index 0000000000..a8d3ba00f4
--- /dev/null
+++ b/src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckNames.cs
@@ -0,0 +1,23 @@
+// 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
+{
+ ///
+ /// Contains names used for health checks in the Azure Functions host.
+ ///
+ internal static class HealthCheckNames
+ {
+ private const string Prefix = "az.functions.";
+
+ ///
+ /// The 'az.functions.web_host.lifecycle' check monitors the lifecycle of the web host.
+ ///
+ public const string WebHostLifeCycle = Prefix + "web_host.lifecycle";
+
+ ///
+ /// The 'az.functions.script_host.lifecycle' check monitors the lifecycle of the script host.
+ ///
+ public const string ScriptHostLifeCycle = Prefix + "script_host.lifecycle";
+ }
+}
diff --git a/src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckTags.cs b/src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckTags.cs
new file mode 100644
index 0000000000..6e28da3af3
--- /dev/null
+++ b/src/WebJobs.Script/Diagnostics/HealthChecks/HealthCheckTags.cs
@@ -0,0 +1,37 @@
+// 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
+{
+ ///
+ /// Contains tags used for health checks in the Azure Functions host.
+ ///
+ internal static class HealthCheckTags
+ {
+ private const string Prefix = "az.functions.";
+
+ ///
+ /// The 'az.functions.liveness' tag is used for liveness checks in the Azure Functions host.
+ ///
+ ///
+ /// Liveness checks are used to determine if the host is alive and responsive.
+ ///
+ public const string Liveness = Prefix + "liveness";
+
+ ///
+ /// The 'az.functions.readiness' tag is used for readiness checks in the Azure Functions host.
+ ///
+ ///
+ /// Readiness checks are used to determine if the host is ready to process requests.
+ ///
+ public const string Readiness = Prefix + "readiness";
+
+ ///
+ /// The 'az.functions.configuration' tag is used for configuration-related health checks in the Azure Functions host.
+ ///
+ ///
+ /// These are typically customer configuration related, such as configuring AzureWebJobsStorage access.
+ ///
+ public const string Configuration = Prefix + "configuration";
+ }
+}
diff --git a/src/WebJobs.Script/Diagnostics/HealthChecks/ScriptHostHealthCheck.cs b/src/WebJobs.Script/Diagnostics/HealthChecks/ScriptHostHealthCheck.cs
index 9315578fc4..050aab0b56 100644
--- a/src/WebJobs.Script/Diagnostics/HealthChecks/ScriptHostHealthCheck.cs
+++ b/src/WebJobs.Script/Diagnostics/HealthChecks/ScriptHostHealthCheck.cs
@@ -8,6 +8,10 @@
namespace Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks
{
+ ///
+ /// A health check that reports the status of the script host lifecycle.
+ ///
+ /// The script host manager.
internal class ScriptHostHealthCheck(IScriptHostManager manager) : IHealthCheck
{
private readonly IScriptHostManager _manager = manager ?? throw new ArgumentNullException(nameof(manager));
@@ -33,6 +37,7 @@ internal class ScriptHostHealthCheck(IScriptHostManager manager) : IHealthCheck
private static readonly Task _unhealthyUnknown =
Task.FromResult(HealthCheckResult.Unhealthy("Script host in unknown state"));
+ ///
public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
=> _manager.State switch
{
diff --git a/src/WebJobs.Script/Diagnostics/HealthChecks/WebHostHealthCheck.cs b/src/WebJobs.Script/Diagnostics/HealthChecks/WebHostHealthCheck.cs
index 4adbb384f8..1b27931d42 100644
--- a/src/WebJobs.Script/Diagnostics/HealthChecks/WebHostHealthCheck.cs
+++ b/src/WebJobs.Script/Diagnostics/HealthChecks/WebHostHealthCheck.cs
@@ -8,6 +8,10 @@
namespace Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks
{
+ ///
+ /// A health check that reports the status of the web host lifecycle.
+ ///
+ /// The application lifetime.
internal class WebHostHealthCheck(IHostApplicationLifetime lifetime) : IHealthCheck
{
private static readonly Task _healthy = Task.FromResult(HealthCheckResult.Healthy());
@@ -15,6 +19,7 @@ internal class WebHostHealthCheck(IHostApplicationLifetime lifetime) : IHealthCh
private static readonly Task _unhealthyStopping = Task.FromResult(HealthCheckResult.Unhealthy("Stopping"));
private static readonly Task _unhealthyStopped = Task.FromResult(HealthCheckResult.Unhealthy("Stopped"));
+ ///
public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
bool isStopped = lifetime.ApplicationStopped.IsCancellationRequested;
diff --git a/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/HealthCheckExtensionsTests.cs b/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/HealthCheckExtensionsTests.cs
new file mode 100644
index 0000000000..b1b20ac56d
--- /dev/null
+++ b/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/HealthCheckExtensionsTests.cs
@@ -0,0 +1,122 @@
+// 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.Linq;
+using System.Reflection;
+using AwesomeAssertions;
+using Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Moq;
+using Xunit;
+
+namespace Microsoft.Azure.WebJobs.Script.Tests.Diagnostics.HealthChecks
+{
+ public class HealthCheckExtensionsTests
+ {
+ [Fact]
+ public void AddWebJobsScriptHealthChecks_ThrowsOnNullBuilder()
+ {
+ IHealthChecksBuilder builder = null;
+ Action act = () => HealthCheckExtensions.AddWebJobsScriptHealthChecks(builder);
+ act.Should().Throw().WithParameterName("builder");
+ }
+
+ [Fact]
+ public void AddWebHostHealthCheck_ThrowsOnNullBuilder()
+ {
+ IHealthChecksBuilder builder = null;
+ Action act = () => HealthCheckExtensions.AddWebHostHealthCheck(builder);
+ act.Should().Throw().WithParameterName("builder");
+ }
+
+ [Fact]
+ public void AddScriptHostHealthCheck_ThrowsOnNullBuilder()
+ {
+ IHealthChecksBuilder builder = null;
+ Action act = () => HealthCheckExtensions.AddScriptHostHealthCheck(builder);
+ act.Should().Throw().WithParameterName("builder");
+ }
+
+ [Fact]
+ public void AddWebJobsScriptHealthChecks_RegistersBothHealthChecks()
+ {
+ // arrange
+ Mock builder = new(MockBehavior.Strict);
+ builder.Setup(b => b.Add(It.IsAny())).Returns(builder.Object);
+
+ // act
+ IHealthChecksBuilder returned = builder.Object.AddWebJobsScriptHealthChecks();
+
+ // assert
+ returned.Should().BeSameAs(builder.Object);
+ builder.Verify(b => b.Add(IsRegistration(
+ HealthCheckNames.WebHostLifeCycle, HealthCheckTags.Liveness)),
+ Times.Once);
+ builder.Verify(b => b.Add(IsRegistration(
+ HealthCheckNames.ScriptHostLifeCycle, HealthCheckTags.Readiness)),
+ Times.Once);
+ builder.VerifyNoOtherCalls();
+ }
+
+ [Fact]
+ public void AddWebHostHealthCheck_RegistersWebHostHealthCheck()
+ {
+ // arrange
+ Mock builder = new(MockBehavior.Strict);
+ builder.Setup(b => b.Add(It.IsAny())).Returns(builder.Object)
+ .Callback((HealthCheckRegistration registration) =>
+ {
+ Type r = registration.Factory.GetMethodInfo().ReturnType;
+ });
+
+ // act
+ IHealthChecksBuilder returned = builder.Object.AddWebHostHealthCheck();
+
+ // assert
+ returned.Should().BeSameAs(builder.Object);
+ builder.Verify(b => b.Add(IsRegistration(
+ HealthCheckNames.WebHostLifeCycle, HealthCheckTags.Liveness)),
+ Times.Once);
+ builder.VerifyNoOtherCalls();
+ }
+
+ [Fact]
+ public void AddScriptHostHealthCheck_RegistersScriptHostHealthCheck()
+ {
+ // arrange
+ Mock builder = new(MockBehavior.Strict);
+ builder.Setup(b => b.Add(It.IsAny())).Returns(builder.Object);
+
+ // act
+ IHealthChecksBuilder returned = builder.Object.AddScriptHostHealthCheck();
+
+ // assert
+ returned.Should().BeSameAs(builder.Object);
+ builder.Verify(b => b.Add(IsRegistration(
+ HealthCheckNames.ScriptHostLifeCycle, HealthCheckTags.Readiness)),
+ Times.Once);
+ builder.VerifyNoOtherCalls();
+ }
+
+ private static HealthCheckRegistration IsRegistration(string name, string tag)
+ where T : IHealthCheck
+ {
+ static bool IsType(HealthCheckRegistration registration)
+ {
+ if (registration.Factory is not { } factory)
+ {
+ return false;
+ }
+
+ return factory.GetMethodInfo().ReturnType == typeof(T);
+ }
+
+ return Match.Create(r =>
+ {
+ return r.Name == name && r.Tags.Contains(tag) && IsType(r);
+ });
+ }
+ }
+}
diff --git a/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/ScriptHostHealthCheckTests.cs b/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/ScriptHostHealthCheckTests.cs
index 2a9203271e..2c71b21765 100644
--- a/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/ScriptHostHealthCheckTests.cs
+++ b/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/ScriptHostHealthCheckTests.cs
@@ -3,7 +3,7 @@
using System;
using System.Threading.Tasks;
-using FluentAssertions;
+using AwesomeAssertions;
using Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Moq;
diff --git a/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/WebHostHealthCheckTests.cs b/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/WebHostHealthCheckTests.cs
index e374e0f816..f01d0186e0 100644
--- a/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/WebHostHealthCheckTests.cs
+++ b/test/WebJobs.Script.Tests/Diagnostics/HealthChecks/WebHostHealthCheckTests.cs
@@ -3,7 +3,7 @@
using System.Threading;
using System.Threading.Tasks;
-using FluentAssertions;
+using AwesomeAssertions;
using Microsoft.Azure.WebJobs.Script.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Hosting;
diff --git a/test/WebJobs.Script.Tests/Diagnostics/OpenTelemetry/OpenTelemetryConfigurationExtensionsTests.cs b/test/WebJobs.Script.Tests/Diagnostics/OpenTelemetry/OpenTelemetryConfigurationExtensionsTests.cs
index 101375605b..34e4ee996e 100644
--- a/test/WebJobs.Script.Tests/Diagnostics/OpenTelemetry/OpenTelemetryConfigurationExtensionsTests.cs
+++ b/test/WebJobs.Script.Tests/Diagnostics/OpenTelemetry/OpenTelemetryConfigurationExtensionsTests.cs
@@ -6,9 +6,9 @@
using System.Diagnostics;
using System.Linq;
using System.Reflection;
+using AwesomeAssertions;
using Azure.Identity;
using Azure.Monitor.OpenTelemetry.Exporter;
-using FluentAssertions;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Azure.WebJobs.Script.Configuration;
diff --git a/test/WebJobs.Script.Tests/Middleware/AppServiceHeaderFixupMiddlewareTests.cs b/test/WebJobs.Script.Tests/Middleware/AppServiceHeaderFixupMiddlewareTests.cs
index f8feff6581..9ee0b6c545 100644
--- a/test/WebJobs.Script.Tests/Middleware/AppServiceHeaderFixupMiddlewareTests.cs
+++ b/test/WebJobs.Script.Tests/Middleware/AppServiceHeaderFixupMiddlewareTests.cs
@@ -2,7 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Threading.Tasks;
-using FluentAssertions;
+using AwesomeAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Script.WebHost.Middleware;
using Microsoft.Extensions.Primitives;
diff --git a/test/WebJobs.Script.Tests/WebJobs.Script.Tests.csproj b/test/WebJobs.Script.Tests/WebJobs.Script.Tests.csproj
index 353a664a30..f0e2eac201 100644
--- a/test/WebJobs.Script.Tests/WebJobs.Script.Tests.csproj
+++ b/test/WebJobs.Script.Tests/WebJobs.Script.Tests.csproj
@@ -21,13 +21,13 @@
+
-