Skip to content

Health Checks in ConnectSoft Microservice Template

Purpose & Overview

Health Checks in the ConnectSoft Microservice Template provide a standardized, comprehensive mechanism for monitoring the operational status of microservices and their dependencies. Health checks enable orchestrators (Kubernetes, Azure App Gateway, load balancers), monitoring systems, and CI/CD pipelines to make informed decisions about service availability, traffic routing, and deployment readiness.

Why Health Checks Matter

Health checks provide critical capabilities:

  • Orchestrator Integration: Kubernetes liveness/readiness probes, Azure App Gateway backend health validation
  • Traffic Management: Load balancers route traffic only to healthy services
  • Observability: Dashboard visualization, alerting, and trend analysis
  • Deployment Safety: Pre-deployment validation and rolling update coordination
  • Testing: Automated acceptance tests for service startup and dependency availability
  • Debugging: Runtime diagnostics for developers and operations teams

Health Checks Philosophy

Health checks in ConnectSoft are production-first, configurable, and integrated with observability systems. Every microservice exposes standardized health endpoints that reflect both internal state and external dependency status.

Architecture Overview

Health checks in ConnectSoft operate at multiple layers:

Health Check System
├── Endpoints (HTTP/gRPC)
│   ├── /health (readiness - all tagged checks)
│   ├── /alive (liveness - process-level checks)
│   └── /startup (startup probe - warmup checks)
├── Registration Layer (HealthChecksExtensions)
│   ├── System Checks (memory, disk, GC)
│   ├── Infrastructure Checks (SQL, MongoDB, Redis)
│   ├── AI Service Checks (Chat Clients, Embedding Generators, Vector Store)
│   ├── Messaging Checks (RabbitMQ, MassTransit, NServiceBus)
│   ├── Actor Model Checks (Orleans cluster, silo, grain, storage)
│   └── Service Checks (SignalR, gRPC, Hangfire)
├── Publishers (Observability)
│   ├── Seq (structured logging)
│   ├── Application Insights (Azure telemetry)
│   └── OpenTelemetry (metrics/traces)
└── UI Dashboard (HealthChecks.UI)
    └── Visual status monitoring and history

Endpoint Structure

Standard Endpoints

The template provides three standardized HTTP endpoints:

Endpoint Purpose Tag Filter HTTP Status Use Case
/health Readiness probe ready 200 OK / 503 Service Unavailable Load balancer routing, deployment validation
/alive Liveness probe live 200 OK / 503 Service Unavailable Kubernetes pod restart decisions
/startup Startup probe startup 200 OK / 503 Service Unavailable Kubernetes initial startup grace period

Endpoint Configuration

Endpoints are mapped in HealthChecksExtensions.cs:

internal static IEndpointRouteBuilder MapMicroserviceHealthChecks(this IEndpointRouteBuilder endpoints)
{
    HealthChecksOptions healthChecksOptions = endpoints.ServiceProvider
        .GetRequiredService<IOptions<HealthChecksOptions>>().Value;

    // Readiness endpoint
    var endpointsBuilder = endpoints.MapHealthChecks("HEALTHCHECK-PATH", new HealthCheckOptions()
    {
        AllowCachingResponses = healthChecksOptions.AllowCachingResponses,
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
        Predicate = r => r.Tags.Contains("ready"),
        ResultStatusCodes =
        {
            [HealthStatus.Healthy] = StatusCodes.Status200OK,
            [HealthStatus.Degraded] = StatusCodes.Status500InternalServerError,
            [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable,
        },
    });

    // Liveness endpoint
    endpointsBuilder = endpoints.MapHealthChecks("/alive", new HealthCheckOptions
    {
        Predicate = r => r.Tags.Contains("live"),
    });

    // Startup endpoint
    endpointsBuilder = endpoints.MapHealthChecks("/startup", new HealthCheckOptions
    {
        Predicate = r => r.Tags.Contains("startup"),
    });

#if UseGrpc
    // gRPC health service
    endpointsBuilder = endpoints.MapGrpcHealthChecksService();
#endif

    return endpoints;
}

Response Format

Health check endpoints return JSON responses:

{
  "status": "Unhealthy",
  "totalDuration": "00:00:00.2431254",
  "entries": {
    "sql": {
      "status": "Healthy",
      "duration": "00:00:00.0129347"
    },
    "orleans-cluster": {
      "status": "Unhealthy",
      "description": "Failed cluster status health check.",
      "duration": "00:00:00.1000980"
    },
    "redis": {
      "status": "Unhealthy",
      "description": "Timeout",
      "duration": "00:00:00.0873943"
    }
  }
}

Status Code Mapping

Health Status HTTP Code Meaning
Healthy 200 OK Service is operational and ready
Degraded 500 Internal Server Error Service is operational but in degraded state
Unhealthy 503 Service Unavailable Service is not operational

Health Check Registration

Centralized Registration

All health checks are registered in HealthChecksExtensions.cs:

internal static IServiceCollection AddMicroserviceHealthChecks(
    this IServiceCollection services, 
    IConfiguration configuration)
{
    IHealthChecksBuilder builder = services.AddHealthChecks();

    // System-level checks
    builder.AddSystemHealthChecks();

#if ResourceMonitoring
    builder.AddAndConfigureResourceUtilizationHealthCheck();
#endif

#if UseNHibernate
    builder.AndAndConfigureNHibernatePersistenceHealthCheck(configuration);
#endif

#if UseMongoDb
    builder.AddMongoDb(/* ... */);
#endif

#if DistributedCacheRedis
    builder.AddRedis(/* ... */);
#endif

#if UseGrpc
    services.AddGrpcHealthChecks(configure =>
    {
        configure.Services.Map(string.Empty, reg => reg.Tags.Contains("ready", StringComparer.OrdinalIgnoreCase));
        configure.Services.Map("all", _ => true);
    });
#endif

#if UseNServiceBus
    builder.AddNServiceBusHealthChecks(configuration);
#endif

#if UseMassTransit
    builder.AddMassTransitHealthChecks(services);
#endif

#if UseOrleans
    builder.AddOrleansHealthChecks();
#endif

#if UseHangFire
    builder.AddHangfireHealthChecks();
#endif

#if UseSignalR
    builder.AddSignalRHealthChecks();
#endif

    // Default liveness check
    builder.AddCheck(
        name: "self",
        () => HealthCheckResult.Healthy(),
        tags: new string[] { "live" });

    // Startup warmup check
    builder.Add(new HealthCheckRegistration(
        name: "startup-gate",
        factory: sp => new DelegateHealthCheck(_ =>
        {
            var gate = sp.GetRequiredService<StartupWarmupGate>();
            return Task.FromResult(
                gate.IsReady
                ? HealthCheckResult.Healthy("Warmup complete.")
                : HealthCheckResult.Unhealthy("Warming up (startup grace period not elapsed)."));
        }),
        failureStatus: HealthStatus.Unhealthy,
        tags: new[] { "startup" }));

#if HealthCheckUI
    if (!IsRunningUnderTestHost())
    {
        services.AddAndConfigureHealthChecksUI(configuration);
    }
#endif

    builder.AddHealthChecksReportsPublishers(OptionsExtensions.HealthChecksOptions);
    return services;
}

System-Level Health Checks

Memory and Process Checks

System checks monitor process-level resources:

private static void AddSystemHealthChecks(this IHealthChecksBuilder builder)
{
    const int oneHundredMb = 104857600; // 100 MB

    // Disk storage health check (cross-platform)
    builder.AddDiskStorageHealthCheck(storage =>
    {
        if (OperatingSystem.IsWindows())
        {
            string rootDrive = Path.GetPathRoot(Environment.SystemDirectory);
            storage.AddDrive(rootDrive, 1024 * 5); // 5 GB free minimum
        }
        else if (OperatingSystem.IsLinux())
        {
            storage.AddDrive("/", 1024 * 5); // 5 GB free minimum
        }
    }, tags: new string[] { "diskstorage" });

    // Process allocated memory
    builder.AddProcessAllocatedMemoryHealthCheck(
        maximumMegabytesAllocated: 1024, 
        tags: new string[] { "allocatedmemory" });

    // Windows-specific checks
    if (OperatingSystem.IsWindows())
    {
        builder
            .AddPrivateMemoryHealthCheck(
                Process.GetCurrentProcess().PrivateMemorySize64 + (oneHundredMb * 10), 
                tags: new string[] { "privatememory" })
            .AddWorkingSetHealthCheck(
                Process.GetCurrentProcess().WorkingSet64 + (oneHundredMb * 10), 
                tags: new string[] { "workingset" })
            .AddVirtualMemorySizeHealthCheck(
                Process.GetCurrentProcess().VirtualMemorySize64 + (oneHundredMb * 10), 
                tags: new string[] { "virtualmemory" });
    }
    else
    {
        // Cross-platform memory check for Linux/Mac
        builder.AddCrossPlatformMemoryHealthCheck();
    }
}

Cross-Platform Memory Check

For non-Windows platforms:

private static void AddCrossPlatformMemoryHealthCheck(this IHealthChecksBuilder builder)
{
    builder.AddCheck(
        "Memory",
        () =>
        {
            var threshold = 1_000_000_000; // 1 GB
            var totalMemory = GC.GetTotalMemory(forceFullCollection: false);

            return totalMemory < threshold
                ? HealthCheckResult.Healthy($"Total memory is {totalMemory / 1_000_000} MB.")
                : HealthCheckResult.Unhealthy($"Total memory is {totalMemory / 1_000_000} MB, exceeds threshold of {threshold / 1_000_000} MB.");
        },
        tags: new string[] { "memory" });
}

Resource Utilization Check

Optional resource monitoring with CPU and memory thresholds:

#if ResourceMonitoring
private static IHealthChecksBuilder AddAndConfigureResourceUtilizationHealthCheck(
    this IHealthChecksBuilder healthChecksBuilder)
{
    if (OperatingSystem.IsWindows())
    {
        return healthChecksBuilder
            .AddResourceUtilizationHealthCheck(
                options =>
                {
                    options.CpuThresholds = new ResourceUsageThresholds
                    {
                        DegradedUtilizationPercentage = 80,
                        UnhealthyUtilizationPercentage = 90,
                    };
                    options.MemoryThresholds = new ResourceUsageThresholds
                    {
                        DegradedUtilizationPercentage = 80,
                        UnhealthyUtilizationPercentage = 90,
                    };
                },
                tags: "resource utilization");
    }

    return healthChecksBuilder;
}
#endif

Database Health Checks

SQL Server (NHibernate)

For SQL Server persistence:

#if UseNHibernate
private static void AndAndConfigureNHibernatePersistenceHealthCheck(
    this IHealthChecksBuilder builder, 
    IConfiguration configuration)
{
    string dbConnectionString = configuration.GetConnectionString(
        OptionsExtensions.PersistenceModelOptions.NHibernate.NHibernateConnectionStringKey);

    builder.AddSqlServer(
        dbConnectionString,
        healthQuery: "SELECT 1;",
        name: "ConnectSoft.MicroserviceTemplate database health check",
        tags: new string[] { "application sqlserver", "ready" });
}
#endif

MongoDB

For MongoDB persistence:

#if UseMongoDb
builder.AddMongoDb(
    clientFactory: (serviceProvider) =>
    {
        if (!string.IsNullOrEmpty(MicroserviceConstants.MongoDbDIKey))
        {
            return serviceProvider.GetRequiredKeyedService<IMongoClient>(MicroserviceConstants.MongoDbDIKey);
        }
        else
        {
            return serviceProvider.GetRequiredService<IMongoClient>();
        }
    },
    databaseNameFactory: (serviceProvider) =>
    {
        return OptionsExtensions.PersistenceModelOptions.MongoDb.DatabaseName;
    },
    name: "ConnectSoft.MicroserviceTemplate mongDb health check",
    tags: new string[] { "application mongodb", "ready" });
#endif

Redis

For distributed caching:

#if DistributedCacheRedis
string redisConnectionString = configuration.GetConnectionString(
    DistributedCacheRedisExtensions.RedisConnectionStringName);
builder.AddRedis(
    redisConnectionString,
    name: "ConnectSoft.MicroserviceTemplate redis health check",
    tags: new string[] { "application redis", "ready" });
#endif

AI Health Checks

Microsoft.Extensions.AI Chat Completion Health Checks

Health checks validate that AI chat completion clients are properly registered:

#if HealthCheck && UseMicrosoftExtensionsAI
private static IHealthChecksBuilder AddMicrosoftExtensionsAIChatCompletionHealthChecks(this IHealthChecksBuilder builder)
{
#if UseOpenAI
    builder.AddCheck(
        name: "OpenAI Chat Client",
        check: sp =>
        {
            var chatClient = sp.GetKeyedService<Microsoft.Extensions.AI.IChatClient>("openAI");
            if (chatClient == null)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy("OpenAI chat client is not registered."));
            }
            return Task.FromResult(HealthCheckResult.Healthy("OpenAI chat client is registered and available."));
        },
        tags: new string[] { "ai", "chat", "openai", "ready" });
#endif
#if UseAzureOpenAI
    builder.AddCheck(
        name: "Azure OpenAI Chat Client",
        check: sp =>
        {
            var chatClient = sp.GetKeyedService<Microsoft.Extensions.AI.IChatClient>("azureOpenAI");
            if (chatClient == null)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy("Azure OpenAI chat client is not registered."));
            }
            return Task.FromResult(HealthCheckResult.Healthy("Azure OpenAI chat client is registered and available."));
        },
        tags: new string[] { "ai", "chat", "azureopenai", "ready" });
#endif
#if UseOllama
    builder.AddCheck(
        name: "Ollama Chat Client",
        check: sp =>
        {
            var chatClient = sp.GetKeyedService<Microsoft.Extensions.AI.IChatClient>("ollama");
            if (chatClient == null)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy("Ollama chat client is not registered."));
            }
            return Task.FromResult(HealthCheckResult.Healthy("Ollama chat client is registered and available."));
        },
        tags: new string[] { "ai", "chat", "ollama", "ready" });
#endif
#if UseMicrosoftExtensionsAIAzureAIInferenceProvider
    builder.AddCheck(
        name: "Azure AI Inference Chat Client",
        check: sp =>
        {
            var chatClient = sp.GetKeyedService<Microsoft.Extensions.AI.IChatClient>("azureAIInference");
            if (chatClient == null)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy("Azure AI Inference chat client is not registered."));
            }
            return Task.FromResult(HealthCheckResult.Healthy("Azure AI Inference chat client is registered and available."));
        },
        tags: new string[] { "ai", "chat", "azureaiinference", "ready" });
#endif
    return builder;
}
#endif

Available Checks: - ✅ OpenAI Chat Client (when UseOpenAI is enabled) - ✅ Azure OpenAI Chat Client (when UseAzureOpenAI is enabled) - ✅ Ollama Chat Client (when UseOllama is enabled) - ✅ Azure AI Inference Chat Client (when UseMicrosoftExtensionsAIAzureAIInferenceProvider is enabled)

Microsoft.Extensions.AI Embedding Generator Health Checks

Health checks validate that embedding generators are properly registered:

#if HealthCheck && UseMicrosoftExtensionsAIEmbedding
private static IHealthChecksBuilder AddMicrosoftExtensionsAIEmbeddingHealthChecks(this IHealthChecksBuilder builder)
{
#if UseMicrosoftExtensionsAIOpenAIEmbeddingProvider
    builder.AddCheck(
        name: "OpenAI Embedding Generator",
        check: sp =>
        {
            var embeddingGenerator = sp.GetKeyedService<Microsoft.Extensions.AI.IEmbeddingGenerator<string, Microsoft.Extensions.AI.Embedding<float>>>("openAI");
            if (embeddingGenerator == null)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy("OpenAI embedding generator is not registered."));
            }
            return Task.FromResult(HealthCheckResult.Healthy("OpenAI embedding generator is registered and available."));
        },
        tags: new string[] { "ai", "embedding", "openai", "ready" });
#endif
#if UseMicrosoftExtensionsAIAzureOpenAIEmbeddingProvider
    builder.AddCheck(
        name: "Azure OpenAI Embedding Generator",
        check: sp =>
        {
            var embeddingGenerator = sp.GetKeyedService<Microsoft.Extensions.AI.IEmbeddingGenerator<string, Microsoft.Extensions.AI.Embedding<float>>>("azureOpenAI");
            if (embeddingGenerator == null)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy("Azure OpenAI embedding generator is not registered."));
            }
            return Task.FromResult(HealthCheckResult.Healthy("Azure OpenAI embedding generator is registered and available."));
        },
        tags: new string[] { "ai", "embedding", "azureopenai", "ready" });
#endif
#if UseMicrosoftExtensionsAIAzureAIInferenceEmbeddingProvider
    builder.AddCheck(
        name: "Azure AI Inference Embedding Generator",
        check: sp =>
        {
            var embeddingGenerator = sp.GetKeyedService<Microsoft.Extensions.AI.IEmbeddingGenerator<string, Microsoft.Extensions.AI.Embedding<float>>>("azureAIInference");
            if (embeddingGenerator == null)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy("Azure AI Inference embedding generator is not registered."));
            }
            return Task.FromResult(HealthCheckResult.Healthy("Azure AI Inference embedding generator is registered and available."));
        },
        tags: new string[] { "ai", "embedding", "azureaiinference", "ready" });
#endif
#if UseMicrosoftExtensionsAIOllamaEmbeddingProvider
    builder.AddCheck(
        name: "Ollama Embedding Generator",
        check: sp =>
        {
            var embeddingGenerator = sp.GetKeyedService<Microsoft.Extensions.AI.IEmbeddingGenerator<string, Microsoft.Extensions.AI.Embedding<float>>>("ollama");
            if (embeddingGenerator == null)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy("Ollama embedding generator is not registered."));
            }
            return Task.FromResult(HealthCheckResult.Healthy("Ollama embedding generator is registered and available."));
        },
        tags: new string[] { "ai", "embedding", "ollama", "ready" });
#endif
    return builder;
}
#endif

Available Checks: - ✅ OpenAI Embedding Generator (when UseMicrosoftExtensionsAIOpenAIEmbeddingProvider is enabled) - ✅ Azure OpenAI Embedding Generator (when UseMicrosoftExtensionsAIAzureOpenAIEmbeddingProvider is enabled) - ✅ Azure AI Inference Embedding Generator (when UseMicrosoftExtensionsAIAzureAIInferenceEmbeddingProvider is enabled) - ✅ Ollama Embedding Generator (when UseMicrosoftExtensionsAIOllamaEmbeddingProvider is enabled)

Vector Store Health Checks

Health checks validate vector store configuration and embedding generator availability:

#if HealthCheck && UseVectorStore
private static IHealthChecksBuilder AddVectorStoreHealthChecks(this IHealthChecksBuilder builder)
{
    builder.AddCheck(
        name: "Vector Store",
        check: sp =>
        {
            var options = OptionsExtensions.MicrosoftExtensionsAIOptions.VectorStore;
            if (options == null || !options.Enabled)
            {
                return Task.FromResult(HealthCheckResult.Healthy("Vector store is disabled."));
            }

            if (string.IsNullOrWhiteSpace(options.EmbeddingGeneratorKey))
            {
                return Task.FromResult(HealthCheckResult.Unhealthy("Vector store is enabled but EmbeddingGeneratorKey is not configured."));
            }

            // Verify embedding generator is registered
            var embeddingGenerator = sp.GetKeyedService<Microsoft.Extensions.AI.IEmbeddingGenerator<string, Microsoft.Extensions.AI.Embedding<float>>>(options.EmbeddingGeneratorKey);
            if (embeddingGenerator == null)
            {
                return Task.FromResult(HealthCheckResult.Unhealthy(
                    $"Vector store is enabled but embedding generator with key '{options.EmbeddingGeneratorKey}' is not registered."));
            }

            return Task.FromResult(HealthCheckResult.Healthy(
                $"Vector store is enabled and embedding generator '{options.EmbeddingGeneratorKey}' is available."));
        },
        tags: new string[] { "ai", "vectordata", "ready" });

    return builder;
}
#endif

Available Checks: - ✅ Vector Store (when UseVectorStore is enabled) - Validates vector store configuration and embedding generator registration

Messaging Health Checks

MassTransit

MassTransit health checks validate transport and persistence:

#if UseMassTransit
private static void AddMassTransitHealthChecks(
    this IHealthChecksBuilder builder, 
    IServiceCollection services)
{
#if UseMassTransitMongoDBPersistence
    MongoClient mongoClient = new MongoClient(
        connectionString: OptionsExtensions.MassTransitOptions.MongoDbPersistence.Connection);
    builder.AddMongoDb(
        clientFactory: (serviceProvider) => mongoClient,
        databaseNameFactory: (serviceProvider) =>
            OptionsExtensions.MassTransitOptions.MongoDbPersistence.DatabaseName,
        name: "ConnectSoft.MicroserviceTemplate MassTransit MongoDb persistence health check",
        tags: new string[] { "masstransit persistence mongodb", "ready" });
#endif

#if UseMassTransitRabbitMQTransport
    var hostOptions = OptionsExtensions.MassTransitOptions.RabbitMqTransport.RabbitMqTransportHost;
    var connectionString = $"amqp://{hostOptions.UserName}:{hostOptions.Password}@{hostOptions.Host}:{hostOptions.Port}/{hostOptions.VirtualHost}";

    services.AddSingleton<IConnection>(sp =>
    {
        var factory = new ConnectionFactory { Uri = new Uri(connectionString) };
        return factory.CreateConnectionAsync().GetAwaiter().GetResult();
    });

    builder.AddRabbitMQ(
        name: "ConnectSoft.MicroserviceTemplate MassTransit RabbitMq transport health check",
        tags: new string[] { "masstransit transport rabbitmq", "ready" });
#endif
}
#endif

NServiceBus

NServiceBus health checks validate transport and persistence databases:

#if UseNServiceBus
private static void AddNServiceBusHealthChecks(
    this IHealthChecksBuilder builder, 
    IConfiguration configuration)
{
#if UseNServiceBusSqlServerTransport
    var nsbTransportConnectionString = configuration.GetConnectionString(
        NServiceBusExtensions.NServiceBusSqlServerTransportConnectionStringKey);
    builder.AddSqlServer(
        nsbTransportConnectionString,
        healthQuery: "SELECT 1;",
        name: "ConnectSoft.MicroserviceTemplate NServiceBus transport database health check",
        tags: new string[] { "nservicebus transport sqlserver", "ready" });
#endif

#if UseNServiceBusSQLPersistence
    var nsbPersistenceConnectionString = configuration.GetConnectionString(
        NServiceBusExtensions.NServiceBusSQLPersistenceConnectionStringKey);
    builder.AddSqlServer(
        nsbPersistenceConnectionString,
        healthQuery: "SELECT 1;",
        name: "ConnectSoft.MicroserviceTemplate NServiceBus persistence database health check",
        tags: new string[] { "nservicebus persistence sqlserver", "ready" });
#endif
}
#endif

Orleans Health Checks

Orleans-specific health checks validate cluster, silo, grain, and storage health:

Registration

#if UseOrleans
private static void AddOrleansHealthChecks(this IHealthChecksBuilder builder)
{
    builder
        .AddCheck<OrleansClusterHealthCheck>(
            name: nameof(OrleansClusterHealthCheck),
            tags: new[] { "orleans", "cluster" })
        .AddCheck<OrleansGrainHealthCheck>(
            name: nameof(OrleansGrainHealthCheck),
            tags: new[] { "orleans", "grain" })
        .AddCheck<OrleansSiloHealthCheck>(
            name: nameof(OrleansSiloHealthCheck),
            tags: new[] { "orleans", "silo" })
        .AddCheck<OrleansStorageHealthCheck>(
            name: nameof(OrleansStorageHealthCheck),
            tags: new[] { "orleans", "storage" });
}
#endif

Orleans Cluster Health Check

Validates cluster membership and silo availability:

public class OrleansClusterHealthCheck(IClusterClient client, ILogger<OrleansClusterHealthCheck> logger)
    : IHealthCheck
{
    private const string DegradedMessage = " silo(s) unavailable.";
    private const string FailedMessage = "Failed cluster status health check.";

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        var manager = this.client.GetGrain<IManagementGrain>(0);

        try
        {
            var hosts = await manager.GetHosts().ConfigureAwait(false);
            var count = hosts.Values.Count(x => x.IsTerminating() || x == SiloStatus.None);
            return count > 0 
                ? HealthCheckResult.Degraded(count + DegradedMessage) 
                : HealthCheckResult.Healthy();
        }
        catch (Exception exception)
        {
            this.logger.LogError(exception: exception, FailedMessage);
            return HealthCheckResult.Unhealthy(FailedMessage, exception);
        }
    }
}

Orleans Silo Health Check

Validates silo-level health participants:

public class OrleansSiloHealthCheck(IEnumerable<IHealthCheckParticipant> participants)
    : IHealthCheck
{
    private static long lastCheckTime = DateTime.UtcNow.ToBinary();

    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, 
        CancellationToken cancellationToken = default)
    {
        var thisLastCheckTime = DateTime.FromBinary(
            Interlocked.Exchange(ref lastCheckTime, DateTime.UtcNow.ToBinary()));

        foreach (var participant in this.participants)
        {
            if (!participant.CheckHealth(thisLastCheckTime, out var reason))
            {
                return Task.FromResult(HealthCheckResult.Degraded(reason));
            }
        }

        return Task.FromResult(HealthCheckResult.Healthy());
    }
}

Orleans Grain Health Check

Pings a known system grain to validate grain responsiveness:

public class OrleansGrainHealthCheck : IHealthCheck
{
    private readonly IClusterClient client;
    private readonly ILogger<OrleansGrainHealthCheck> logger;

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var grain = this.client.GetGrain<ILocalHealthCheckGrain>(0);
            var result = await grain.CheckHealthAsync(cancellationToken).ConfigureAwait(false);

            return result
                ? HealthCheckResult.Healthy()
                : HealthCheckResult.Unhealthy("Grain health check failed");
        }
        catch (Exception ex)
        {
            this.logger.LogError(ex, "Orleans grain health check failed");
            return HealthCheckResult.Unhealthy("Grain health check exception", ex);
        }
    }
}

Orleans Storage Health Check

Validates grain storage backend availability:

public class OrleansStorageHealthCheck : IHealthCheck
{
    private readonly IClusterClient client;
    private readonly ILogger<OrleansStorageHealthCheck> logger;

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var grain = this.client.GetGrain<IStorageHealthCheckGrain>(Guid.NewGuid());
            var result = await grain.CheckStorageHealthAsync(cancellationToken).ConfigureAwait(false);

            return result
                ? HealthCheckResult.Healthy()
                : HealthCheckResult.Unhealthy("Storage health check failed");
        }
        catch (Exception ex)
        {
            this.logger.LogError(ex, "Orleans storage health check failed");
            return HealthCheckResult.Unhealthy("Storage health check exception", ex);
        }
    }
}

SignalR Health Checks

SignalR health checks validate hub connectivity:

#if UseSignalR
private static void AddSignalRHealthChecks(this IHealthChecksBuilder builder)
{
    builder.Add(new HealthCheckRegistration(
        name: "ConnectSoft.MicroserviceTemplate SignalR health check",
        factory: serviceProvider =>
        {
            // Skip check during warmup
            var warmup = serviceProvider.GetRequiredService<StartupWarmupGate>();
            if (!warmup.IsReady)
            {
                return new DelegateHealthCheck(_ =>
                    Task.FromResult(HealthCheckResult.Healthy("Startup warmup (SignalR check skipped)")));
            }

            // Get server address
            var server = serviceProvider.GetRequiredService<IServer>();
            var addresses = server.Features.Get<IServerAddressesFeature>()?.Addresses;
            var baseAddr = addresses?.FirstOrDefault(a => a.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
                        ?? addresses?.FirstOrDefault();

            if (string.IsNullOrEmpty(baseAddr))
            {
                baseAddr = Environment.GetEnvironmentVariable("ASPNETCORE_URLS")
                   ?.Split(';', StringSplitOptions.RemoveEmptyEntries)
                   .FirstOrDefault();
            }

            var opts = serviceProvider.GetService<IOptions<SignalRHealthCheckClientOptions>>()?.Value
                ?? new SignalRHealthCheckClientOptions();

            var url = new Uri(baseAddr, UriKind.Absolute);
            var host = (url.Host is "+" or "0.0.0.0" or "::" or "[::]") ? "127.0.0.1" : url.Host;
            var hubPath = string.IsNullOrWhiteSpace(opts.HubPath) ? "/webChatHub" : opts.HubPath;
            var hubUri = new UriBuilder(url.Scheme, host, url.Port, hubPath.TrimStart('/')).Uri.ToString();

            return new SignalRHealthCheck(() =>
            {
                IHubConnectionBuilder builder = new HubConnectionBuilder();
                opts.ConfigureBuilder?.Invoke(serviceProvider, builder);

                builder = builder
                    .WithUrl(hubUri, http => opts.ConfigureHttp?.Invoke(serviceProvider, http))
                    .WithAutomaticReconnect();

                return builder.Build();
            });
        },
        failureStatus: HealthStatus.Unhealthy,
        tags: new string[] { "signalr" },
        timeout: TimeSpan.FromSeconds(10)));

#if UseRedisSignalRBackplane
    builder.AddRedis(
        OptionsExtensions.SignalROptions.RedisSignalRBackplaneOptions.ConfigurationString,
        name: "ConnectSoft.MicroserviceTemplate SignalR Redis backplane health check",
        tags: new string[] { "signalr", "backplane", "redis" });
#endif
}
#endif

gRPC Health Check Protocol

The template implements the official gRPC Health Checking Protocol:

Service Registration

#if UseGrpc
services.AddGrpcHealthChecks(configure =>
{
    // Overall service ("") = checks tagged 'ready'
    configure.Services.Map(string.Empty, reg => reg.Tags.Contains("ready", StringComparer.OrdinalIgnoreCase));

    // Named mapping that aggregates all checks
    configure.Services.Map("all", _ => true);
});

// Map gRPC health service endpoint
endpoints.MapGrpcHealthChecksService();
#endif

Usage

Clients can check service health via gRPC:

var client = new Health.HealthClient(channel);
var response = await client.CheckAsync(new HealthCheckRequest { Service = "" });

if (response.Status == HealthCheckResponse.Types.ServingStatus.Serving)
{
    // Service is healthy
}

Tag-Based Filtering

Health checks use tags to organize and filter checks:

Standard Tags

Tag Purpose Used By
ready Service readiness /health endpoint, load balancers
live Process liveness /alive endpoint, Kubernetes liveness probes
startup Startup warmup /startup endpoint, Kubernetes startup probes
orleans Orleans-specific checks Filtering Orleans health status
storage Database/persistence Filtering storage health
infra Infrastructure components General infrastructure grouping
optional Non-critical checks Degraded mode scenarios

Tag Usage in Checks

// Readiness check
builder.AddSqlServer(/* ... */, tags: new string[] { "application sqlserver", "ready" });

// Liveness check
builder.AddCheck("self", () => HealthCheckResult.Healthy(), tags: new string[] { "live" });

// Orleans checks
builder.AddCheck<OrleansClusterHealthCheck>(/* ... */, tags: new[] { "orleans", "cluster" });

// Optional check (won't fail overall health)
builder.AddCheck<ExternalApiHealthCheck>(/* ... */, tags: new[] { "optional", "external" });

Health Checks UI

HealthChecks.UI provides a visual dashboard for health monitoring:

Configuration

#if HealthCheckUI
private static void AddAndConfigureHealthChecksUI(
    this IServiceCollection services, 
    IConfiguration configuration)
{
    HealthChecksUIBuilder healthChecksUIBuilder = services.AddHealthChecksUI(setupOptions =>
    {
        setupOptions.UseApiEndpointHttpMessageHandler((provider) =>
        {
            var handler = new HttpClientHandler();
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.ServerCertificateCustomValidationCallback = 
                HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
            return handler;
        });
    });

#if UseSqlServerHealthCheckUIStorageProvider
    string healthChecksUIConnectionString = configuration.GetConnectionString(
        "ConnectSoft.MicroserviceTemplateHealthCheckUI");
    healthChecksUIBuilder.AddSqlServerStorage(connectionString: healthChecksUIConnectionString);
#endif
}

// Map UI endpoint
endpoints.MapHealthChecksUI(setup =>
{
    setup.UIPath = "/" + HealthCheckUIPath;
    setup.PageTitle = "ConnectSoft.MicroserviceTemplate Health Checks UI";
});
#endif

Accessing the UI

The UI is accessible at /HEALTHCHECK-PATH-UI (configurable via template options).

Health Check Publishers

Publishers send health check results to external observability systems:

Seq Publisher

Publishes health check results to Seq:

#if UseSeqAsHealthChecksPublisher
private static void AddHealthChecksReportsPublishers(
    this IHealthChecksBuilder builder, 
    HealthChecksOptions healthChecksOptions)
{
    builder.AddSeqPublisher(setup =>
    {
        setup.Endpoint = healthChecksOptions.SeqPublisher.Endpoint;
        setup.ApiKey = healthChecksOptions.SeqPublisher.ApiKey;
        setup.DefaultInputLevel = HealthChecks.Publisher.Seq.SeqInputLevel.Debug;
    });
}
#endif

Application Insights Publisher

Publishes to Azure Application Insights:

#if UseApplicationInsightsAsHealthChecksPublisher
builder.AddApplicationInsightsPublisher(
    connectionString: healthChecksOptions.ApplicationInsightsPublisher.ConnectionString,
    saveDetailedReport: healthChecksOptions.ApplicationInsightsPublisher.SaveDetailedReport,
    excludeHealthyReports: healthChecksOptions.ApplicationInsightsPublisher.ExcludeHealthyReports);
#endif

Configuration

{
  "HealthChecks": {
    "SeqPublisher": {
      "Endpoint": "https://seq.example.com",
      "ApiKey": "optional-api-key"
    },
    "ApplicationInsightsPublisher": {
      "ConnectionString": "InstrumentationKey=...",
      "SaveDetailedReport": true,
      "ExcludeHealthyReports": false
    }
  }
}

Custom Health Checks

Creating Custom Checks

Implement IHealthCheck interface:

public class ExternalApiHealthCheck : IHealthCheck
{
    private readonly IHttpClientFactory httpClientFactory;
    private readonly ILogger<ExternalApiHealthCheck> logger;

    public ExternalApiHealthCheck(
        IHttpClientFactory httpClientFactory,
        ILogger<ExternalApiHealthCheck> logger)
    {
        this.httpClientFactory = httpClientFactory;
        this.logger = logger;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var client = this.httpClientFactory.CreateClient("ExternalService");
            var response = await client.GetAsync("/status", cancellationToken);

            return response.IsSuccessStatusCode
                ? HealthCheckResult.Healthy()
                : HealthCheckResult.Unhealthy($"External API returned {response.StatusCode}");
        }
        catch (Exception ex)
        {
            this.logger.LogError(ex, "External API health check failed");
            return HealthCheckResult.Unhealthy("External API check exception", ex);
        }
    }
}

Registering Custom Checks

builder.AddCheck<ExternalApiHealthCheck>(
    "external-api",
    tags: new[] { "ready", "optional", "external" });

Delegate-Based Checks

For simple checks, use DelegateHealthCheck:

builder.AddCheck("feature-toggle", () =>
{
    return FeatureFlagManager.IsEnabled("Payments")
        ? HealthCheckResult.Healthy()
        : HealthCheckResult.Degraded("Payments feature is disabled");
}, tags: new[] { "optional" });

Configuration Options

HealthChecksOptions

public sealed class HealthChecksOptions
{
    public const string HealthChecksOptionsSectionName = "HealthChecks";

    /// <summary>
    /// Whether responses from the health check middleware can be cached.
    /// </summary>
    [Required]
    required public bool AllowCachingResponses { get; set; }

#if UseSeqAsHealthChecksPublisher
    [Required]
    [ValidateObjectMembers]
    required public HealthChecksSeqPublisherOptions SeqPublisher { get; set; }
#endif

#if UseApplicationInsightsAsHealthChecksPublisher
    [Required]
    [ValidateObjectMembers]
    required public HealthChecksApplicationInsightsPublisherOptions ApplicationInsightsPublisher { get; set; }
#endif
}

appsettings.json Example

{
  "HealthChecks": {
    "AllowCachingResponses": false,
    "SeqPublisher": {
      "Endpoint": "https://seq.example.com",
      "ApiKey": "optional-key"
    },
    "ApplicationInsightsPublisher": {
      "ConnectionString": "InstrumentationKey=...",
      "SaveDetailedReport": true,
      "ExcludeHealthyReports": false
    }
  }
}

Kubernetes Integration

Deployment Configuration

Health checks integrate with Kubernetes probes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: microservice
spec:
  template:
    spec:
      containers:
      - name: microservice
        image: microservice:latest
        livenessProbe:
          httpGet:
            path: /alive
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /health
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3
        startupProbe:
          httpGet:
            path: /startup
            port: 80
          initialDelaySeconds: 0
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 30

Probe Behavior

  • Liveness Probe (/alive): If fails, Kubernetes restarts the pod
  • Readiness Probe (/health): If fails, Kubernetes removes pod from service endpoints
  • Startup Probe (/startup): Gives slow-starting services time to initialize

Testing

Acceptance Tests

Health checks are validated via Reqnroll feature tests:

[Binding]
public sealed class TestHealthCheckEndpointStepBinding
{
    [When(@"I check the health of the application")]
    public async Task WhenICheckTheHealthOfTheApplication()
    {
        using HttpClient? httpClient = BeforeAfterTestRunHooks.ServerInstance?.CreateClient();

        this.healthCheckHttpResponse = await WaitForHealthyAsync(
            httpClient,
            "HEALTHCHECK-PATH",
            ReadyTimeout,
            PollInterval).ConfigureAwait(false);
    }

    [Then(@"I should receive a 200 OK response")]
    public async Task ThenIShouldReceiveAOKResponse()
    {
        Assert.IsNotNull(this.healthCheckHttpResponse);
        string content = await this.healthCheckHttpResponse.Content.ReadAsStringAsync();

        Assert.IsTrue(this.healthCheckHttpResponse.IsSuccessStatusCode, "Response: " + content);
        Assert.AreEqual(HttpStatusCode.OK, this.healthCheckHttpResponse.StatusCode);
    }

    private static async Task<HttpResponseMessage> WaitForHealthyAsync(
        HttpClient client,
        string path,
        TimeSpan timeout,
        TimeSpan pollInterval)
    {
        var deadline = DateTime.UtcNow + timeout;
        HttpResponseMessage? last = null;

        while (DateTime.UtcNow < deadline)
        {
            last?.Dispose();
            last = await client.GetAsync(path).ConfigureAwait(false);
            var body = await last.Content.ReadAsStringAsync().ConfigureAwait(false);

            if (last.IsSuccessStatusCode && IsHealthy(body))
            {
                return last;
            }

            await Task.Delay(pollInterval).ConfigureAwait(false);
        }

        last?.Dispose();
        throw new TimeoutException($"Health check did not become healthy within {timeout}");
    }

    private static bool IsHealthy(string jsonBody)
    {
        // Parse JSON and check status field
        // Implementation details...
        return jsonBody.Contains("\"status\":\"Healthy\"", StringComparison.OrdinalIgnoreCase);
    }
}

gRPC Health Check Tests

[Binding]
public sealed class TestGrpcHealthCheckProtocolStepDefinition
{
    [When(@"the gRPC client calls Health.Check")]
    public async Task WhenTheGrpcClientCallsHealthCheck()
    {
        var response = await this.grpcHealthClient.CheckAsync(
            new HealthCheckRequest { Service = "" });

        this.grpcHealthResponse = response;
    }

    [Then(@"the response status should be SERVING")]
    public void ThenTheResponseStatusShouldBeServing()
    {
        this.grpcHealthResponse.Status.Should().Be(
            HealthCheckResponse.Types.ServingStatus.Serving);
    }
}

Security Considerations

Endpoint Protection

Health check endpoints should be protected appropriately:

// Protect /health endpoint (internal diagnostics)
app.MapHealthChecks("/health", new HealthCheckOptions { /* ... */ })
    .RequireAuthorization("HealthChecksPolicy");

// Keep /alive public (Kubernetes needs access)
app.MapHealthChecks("/alive", new HealthCheckOptions { /* ... */ });

Best Practices

Practice Reason
Restrict /health to internal networks Prevents infrastructure information disclosure
Keep /alive public or IP-whitelisted Required for orchestrator liveness probes
Sanitize error messages Avoid exposing sensitive details in health responses
Use authentication for /health-ui Dashboard may contain sensitive operational data
Avoid connection strings in responses Prevent credential exposure
Disable stack traces in production Prevent code path disclosure

Secure Response Example

return HealthCheckResult.Unhealthy(
    description: "Database connection failed"); // Generic, no details

// Avoid:
return HealthCheckResult.Unhealthy(
    description: $"Connection failed: Server=db.example.com;User=admin;Password=secret");

Best Practices

Do's

  1. Use Appropriate Tags
  2. Tag checks as ready for traffic routing decisions
  3. Tag checks as live for process-level health
  4. Use optional for non-critical dependencies

  5. Keep Checks Fast

  6. Health checks should complete in < 1 second
  7. Use simple queries (e.g., SELECT 1)
  8. Avoid expensive operations

  9. Handle Failures Gracefully

  10. Catch and log exceptions
  11. Return appropriate health status
  12. Don't throw exceptions from health checks

  13. Monitor Health Check Metrics

  14. Track health check duration
  15. Alert on repeated failures
  16. Correlate health failures with application logs

  17. Use Conditional Registration

  18. Only register checks for enabled features
  19. Use preprocessor directives for optional features
  20. Avoid unnecessary check overhead

Don'ts

  1. Don't Use Expensive Queries

    // ❌ BAD - Expensive query
    builder.AddSqlServer(connectionString, healthQuery: "SELECT * FROM LargeTable");
    
    // ✅ GOOD - Simple query
    builder.AddSqlServer(connectionString, healthQuery: "SELECT 1");
    

  2. Don't Expose Sensitive Data

    // ❌ BAD - Exposes connection string
    return HealthCheckResult.Unhealthy($"Failed: {connectionString}");
    
    // ✅ GOOD - Generic error
    return HealthCheckResult.Unhealthy("Database connection failed");
    

  3. Don't Block Health Checks

    // ❌ BAD - Synchronous blocking
    response.Wait();
    
    // ✅ GOOD - Async/await
    await response;
    

  4. Don't Register Unnecessary Checks

    // ❌ BAD - Always registered
    builder.AddSqlServer(connectionString);
    
    // ✅ GOOD - Conditional
    #if UseNHibernate
    builder.AddSqlServer(connectionString);
    #endif
    

Complete Health Check Reference

System-Level Health Checks

Check Condition Tags Description
Disk Storage Always diskstorage Validates available disk space (5 GB minimum)
Process Allocated Memory Always allocatedmemory Monitors process memory allocation (1024 MB max)
Private Memory Windows only privatememory Windows-specific private memory check
Working Set Windows only workingset Windows-specific working set check
Virtual Memory Windows only virtualmemory Windows-specific virtual memory check
Cross-Platform Memory Linux/Mac only memory Cross-platform memory check (1 GB threshold)
Resource Utilization ResourceMonitoring resource utilization CPU and memory utilization thresholds (Windows only)
Self Always live Basic liveness check
Startup Gate Always startup Validates startup warmup completion

Database Health Checks

Check Condition Tags Description
SQL Server (NHibernate) UseNHibernate application sqlserver, ready Validates SQL Server database connectivity
MongoDB UseMongoDb application mongodb, ready Validates MongoDB connectivity
Redis DistributedCacheRedis application redis, ready Validates Redis connectivity

Messaging Health Checks

Check Condition Tags Description
NServiceBus SQL Transport UseNServiceBus + UseNServiceBusSqlServerTransport nservicebus transport sqlserver, ready Validates NServiceBus SQL Server transport database
NServiceBus SQL Persistence UseNServiceBus + UseNServiceBusSQLPersistence nservicebus persistence sqlserver, ready Validates NServiceBus SQL Server persistence database
MassTransit MongoDB Persistence UseMassTransit + UseMassTransitMongoDBPersistence masstransit persistence mongodb, ready Validates MassTransit MongoDB persistence
MassTransit RabbitMQ Transport UseMassTransit + UseMassTransitRabbitMQTransport masstransit transport rabbitmq, ready Validates MassTransit RabbitMQ transport connectivity

Actor Model Health Checks (Orleans)

Check Condition Tags Description
Orleans Cluster UseOrleans orleans, cluster Validates Orleans cluster membership and silo availability
Orleans Grain UseOrleans orleans, grain Validates grain responsiveness
Orleans Silo UseOrleans orleans, silo Validates silo-level health participants
Orleans Storage UseOrleans orleans, storage Validates grain storage backend availability
Orleans Azure Table Storage Clustering UseOrleans + Azure Table Storage clustering orleans, clustering, azure-table-storage, ready Validates Azure Table Storage clustering connectivity
Orleans ADO.NET Clustering UseOrleans + ADO.NET clustering orleans, clustering, adonet, ready Validates ADO.NET clustering database connectivity
Orleans Redis Clustering UseOrleans + Redis clustering orleans, clustering, redis, ready Validates Redis clustering connectivity
Orleans Azure Table Storage Transactional State UseOrleans + Azure Table Storage transactional state orleans, transactional-state, azure-table-storage, ready Validates Azure Table Storage transactional state storage

Service Health Checks

Check Condition Tags Description
SignalR Hub UseSignalR signalr Validates SignalR hub connectivity
SignalR Redis Backplane UseSignalR + UseRedisSignalRBackplane signalr, backplane, redis Validates SignalR Redis backplane connectivity
Hangfire UseHangFire application hangfire Validates Hangfire job processing availability
gRPC Health Service UseGrpc ready Official gRPC health checking protocol

AI Health Checks

Check Condition Tags Description
OpenAI Chat Client UseMicrosoftExtensionsAI + UseOpenAI ai, chat, openai, ready Validates OpenAI chat client registration
Azure OpenAI Chat Client UseMicrosoftExtensionsAI + UseAzureOpenAI ai, chat, azureopenai, ready Validates Azure OpenAI chat client registration
Ollama Chat Client UseMicrosoftExtensionsAI + UseOllama ai, chat, ollama, ready Validates Ollama chat client registration
Azure AI Inference Chat Client UseMicrosoftExtensionsAI + UseMicrosoftExtensionsAIAzureAIInferenceProvider ai, chat, azureaiinference, ready Validates Azure AI Inference chat client registration
OpenAI Embedding Generator UseMicrosoftExtensionsAIEmbedding + UseMicrosoftExtensionsAIOpenAIEmbeddingProvider ai, embedding, openai, ready Validates OpenAI embedding generator registration
Azure OpenAI Embedding Generator UseMicrosoftExtensionsAIEmbedding + UseMicrosoftExtensionsAIAzureOpenAIEmbeddingProvider ai, embedding, azureopenai, ready Validates Azure OpenAI embedding generator registration
Azure AI Inference Embedding Generator UseMicrosoftExtensionsAIEmbedding + UseMicrosoftExtensionsAIAzureAIInferenceEmbeddingProvider ai, embedding, azureaiinference, ready Validates Azure AI Inference embedding generator registration
Ollama Embedding Generator UseMicrosoftExtensionsAIEmbedding + UseMicrosoftExtensionsAIOllamaEmbeddingProvider ai, embedding, ollama, ready Validates Ollama embedding generator registration
Vector Store UseVectorStore ai, vectordata, ready Validates vector store configuration and embedding generator availability

Summary

Health Checks in the ConnectSoft Microservice Template provide:

  • Standardized Endpoints: /health, /alive, /startup with consistent JSON responses
  • Comprehensive Coverage: System, database, messaging, actor model, AI services, and service checks
  • Tag-Based Filtering: Organize checks by purpose (ready, live, startup, optional)
  • Orchestrator Integration: Kubernetes probes, load balancer health validation
  • Observability: Seq, Application Insights, and OpenTelemetry publishers
  • Visual Dashboard: HealthChecks.UI for development and operations
  • gRPC Protocol: Official gRPC health checking protocol support
  • AI Service Monitoring: Health checks for chat completions, embedding generators, and vector stores
  • Customizable: Easy to add custom checks for domain-specific scenarios
  • Secure: Configurable endpoint protection and sanitized responses
  • Testable: Acceptance tests validate health check behavior

By following these patterns, health checks enable reliable, observable, and maintainable microservice operations, ensuring services remain available and dependencies are properly monitored across the entire system lifecycle.