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; /// /// Health checks split into liveness (is the process up?) and readiness (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. /// /// /healthz/live — process only (always healthy while the host runs); never touches a dependency. /// /healthz/ready — the app DB, the log DB (deployed only), and the object-storage write probe. /// /HealthCheck — the aggregate (every check), retained for backward compatibility. /// /// 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("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; } }