Monitor application health and readiness with built-in health check endpoints
FullStackHero includes comprehensive health check endpoints using ASP.NET Core’s health check system. Health checks allow monitoring tools, orchestrators (like Kubernetes), and load balancers to determine if your application is running correctly.
The health check endpoints are defined in the Web building block:
HealthEndpoints.cs
public static IEndpointRouteBuilder MapHeroHealthEndpoints( this IEndpointRouteBuilder app){ var group = app.MapGroup("/health") .WithTags("Health") .AllowAnonymous() .DisableRateLimiting(); // Liveness: only process up (no external deps) group.MapGet("/live", async Task<Ok<HealthResult>> ( HealthCheckService hc, CancellationToken cancellationToken) => { var report = await hc.CheckHealthAsync( _ => false, cancellationToken); var payload = new HealthResult( Status: report.Status.ToString(), Results: Array.Empty<HealthEntry>()); return TypedResults.Ok(payload); }) .WithName("Liveness") .WithSummary("Quick process liveness probe.") .WithDescription("Reports if the API process is alive. Does not check dependencies.") .Produces<HealthResult>(StatusCodes.Status200OK); // Readiness: includes DB (and any other registered checks) group.MapGet("/ready", async Task<Results<Ok<HealthResult>, StatusCodeHttpResult>> ( HealthCheckService hc, CancellationToken cancellationToken) => { var report = await hc.CheckHealthAsync( cancellationToken: cancellationToken); var results = report.Entries.Select(e => new HealthEntry( Name: e.Key, Status: e.Value.Status.ToString(), Description: e.Value.Description, DurationMs: e.Value.Duration.TotalMilliseconds, Details: e.Value.Data.ToDictionary( k => k.Key, v => v.Value is null ? "null" : v.Value ))); var payload = new HealthResult( report.Status.ToString(), results); return report.Status == HealthStatus.Healthy ? TypedResults.Ok(payload) : TypedResults.StatusCode( StatusCodes.Status503ServiceUnavailable); }) .WithName("Readiness") .WithSummary("Readiness probe with database check.") .WithDescription("Returns 200 if all dependencies are healthy, otherwise 503.") .Produces<HealthResult>(StatusCodes.Status200OK) .Produces(StatusCodes.Status503ServiceUnavailable); return app;}
public class TenantMigrationsHealthCheck : IHealthCheck{ private readonly IServiceProvider _serviceProvider; public TenantMigrationsHealthCheck(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public async Task<HealthCheckResult> CheckHealthAsync( HealthCheckContext context, CancellationToken ct = default) { try { // Check if all tenant databases have migrations applied using var scope = _serviceProvider.CreateScope(); var tenantStore = scope.ServiceProvider .GetRequiredService<ITenantStore>(); var tenants = await tenantStore.GetAllAsync(); var unhealthyTenants = new List<string>(); foreach (var tenant in tenants) { var dbContext = GetTenantDbContext(tenant.Id); var pendingMigrations = await dbContext.Database .GetPendingMigrationsAsync(ct); if (pendingMigrations.Any()) { unhealthyTenants.Add(tenant.Identifier); } } if (unhealthyTenants.Any()) { return HealthCheckResult.Degraded( $"Tenants with pending migrations: {string.Join(", ", unhealthyTenants)}"); } return HealthCheckResult.Healthy( "All tenant databases are up to date"); } catch (Exception ex) { return HealthCheckResult.Unhealthy( "Failed to check tenant migrations", ex); } }}
Create dashboards to visualize health check results:
# Health check status (1 = healthy, 0 = unhealthy)health_check_status{name="database"}# Health check durationhealth_check_duration_seconds{name="database"}