81 lines
3.7 KiB
C#
81 lines
3.7 KiB
C#
using Baya.Infrastructure.Monitoring.HealthChecks;
|
|
using HealthChecks.UI.Client;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
namespace Baya.Infrastructure.Monitoring.Configurations;
|
|
|
|
/// <summary>
|
|
/// Health checks split into <b>liveness</b> (is the process up?) and <b>readiness</b> (can it serve traffic —
|
|
/// are its dependencies reachable?), refinement-phase-9 §9.2. An orchestrator restarts on a failed liveness
|
|
/// probe but only routes traffic on a passing readiness probe, so a broken log DB or object storage takes the
|
|
/// instance out of rotation without a restart loop.
|
|
/// <list type="bullet">
|
|
/// <item><c>/healthz/live</c> — process only (always healthy while the host runs); never touches a dependency.</item>
|
|
/// <item><c>/healthz/ready</c> — the app DB, the log DB (deployed only), and the object-storage write probe.</item>
|
|
/// <item><c>/HealthCheck</c> — the aggregate (every check), retained for backward compatibility.</item>
|
|
/// </list>
|
|
/// </summary>
|
|
public static class HealthCheckConfigurations
|
|
{
|
|
private const string Ready = "ready";
|
|
private const string Live = "live";
|
|
|
|
public static WebApplicationBuilder ConfigureHealthChecks(this WebApplicationBuilder builder)
|
|
{
|
|
var checks = builder.Services.AddHealthChecks();
|
|
|
|
// Liveness: the process answers. Deliberately dependency-free so a dependency outage never restarts a
|
|
// healthy instance — that is readiness's job.
|
|
checks.AddCheck("self", () => HealthCheckResult.Healthy(), tags: [Live]);
|
|
|
|
// Readiness: the system of record.
|
|
checks.AddSqlServer(builder.Configuration.GetConnectionString("SqlServer")!, name: "sql-app", tags: [Ready]);
|
|
|
|
// The Serilog SQL sink only runs in deployed environments (Development/Testing log to console/file), and
|
|
// its connection string is a placeholder there — so only gate readiness on it where it is actually used.
|
|
if (!builder.Environment.IsDevelopment() && !builder.Environment.IsEnvironment("Testing"))
|
|
{
|
|
var logDb = builder.Configuration.GetConnectionString("logDb");
|
|
if (!string.IsNullOrWhiteSpace(logDb))
|
|
checks.AddSqlServer(logDb, name: "sql-logs", tags: [Ready]);
|
|
}
|
|
|
|
// Object storage (verification docs, avatars, invoice PDFs) — a real write round-trip.
|
|
checks.AddCheck<ObjectStorageWriteHealthCheck>("object-storage", tags: [Ready]);
|
|
|
|
// Redis (ICacheService / IDistributedLock) is single-process today (refinement-phase-7); when it becomes a
|
|
// real external dependency for a multi-instance deployment, add a "redis" check tagged Ready here.
|
|
|
|
return builder;
|
|
}
|
|
|
|
public static WebApplication UseHealthChecks(this WebApplication app)
|
|
{
|
|
app.MapHealthChecks("/healthz/live", new HealthCheckOptions
|
|
{
|
|
Predicate = check => check.Tags.Contains(Live),
|
|
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
|
}).ShortCircuit();
|
|
|
|
app.MapHealthChecks("/healthz/ready", new HealthCheckOptions
|
|
{
|
|
Predicate = check => check.Tags.Contains(Ready),
|
|
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
|
}).ShortCircuit();
|
|
|
|
// Aggregate endpoint (all checks) — retained for backward compatibility with existing probes/dashboards.
|
|
app.MapHealthChecks("/HealthCheck", new HealthCheckOptions
|
|
{
|
|
Predicate = _ => true,
|
|
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
|
|
}).ShortCircuit();
|
|
|
|
return app;
|
|
}
|
|
}
|