-
Notifications
You must be signed in to change notification settings - Fork 1
Healthcheck
IOF edited this page Nov 28, 2025
·
2 revisions
builder.Services.AddHealthCheckServices(config);public static class HealthCheckServices
{
public static IServiceCollection AddHealthCheckServices(
this IServiceCollection services,
IConfiguration config
)
{
services
.AddHealthChecks()
// Проверка подключения к БД
.AddNpgSql(config.GetConnectionString("PostgreSQL")!, name: "Postgres")
// Проверка RabbitMQ
.AddCheck<RabbitMqHealthCheck>("RabbitMQ")
// Проверка Outbox
.AddCheck<OutboxHealthCheck>("OutboxPending");
return services;
}
}
public class RabbitMqHealthCheck : IHealthCheck
{
private readonly IOptions<EventBusOptions> _options;
private readonly ILogger<RabbitMqHealthCheck> _logger;
public RabbitMqHealthCheck(
IOptions<EventBusOptions> options,
ILogger<RabbitMqHealthCheck> logger
)
{
_options = options;
_logger = logger;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default
)
{
try
{
var rmqConnection = new EventBusConnection(_options);
var rmqChannel = await rmqConnection.CreateChannelAsync();
await rmqChannel.CloseAsync();
_logger.LogInformation("HEALTH CHECK: RabbitMQ connection OK");
return HealthCheckResult.Healthy();
}
catch (Exception ex)
{
_logger.LogError(ex, "HEALTH CHECK: RabbitMQ connection failed");
return HealthCheckResult.Unhealthy(ex.Message);
}
}
}
public class OutboxHealthCheck : IHealthCheck
{
private readonly ModuleBankAppContext _db;
private readonly ILogger<OutboxHealthCheck> _logger;
public OutboxHealthCheck(ModuleBankAppContext db, ILogger<OutboxHealthCheck> logger)
{
_db = db;
_logger = logger;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default
)
{
var pendingCount = await _db.Outbox.CountAsync(
e => e.Status == OutboxStatus.Pending,
cancellationToken
);
if (pendingCount > 100)
{
_logger.LogWarning(
"HEALTH CHECK: Outbox backlog {PendingCount} pending events",
pendingCount
);
return HealthCheckResult.Degraded($"Outbox backlog: {pendingCount}");
}
_logger.LogInformation("HEALTH CHECK: Outbox backlog OK ({PendingCount})", pendingCount);
return HealthCheckResult.Healthy($"Outbox backlog: {pendingCount}");
}
}public static class HealthMiddleware
{
public static WebApplication UseHealthMiddleware(this WebApplication app)
{
// Live и Ready endpoints
app.MapHealthChecks(
"/health/live",
new HealthCheckOptions
{
Predicate = _ => false, // только сервис жив
ResponseWriter = async (context, report) =>
{
var correlationId =
context.Request.Headers["X-Correlation-Id"].FirstOrDefault()
?? Guid.NewGuid().ToString();
var log = new
{
Path = context.Request.Path,
Method = context.Request.Method,
Status = report.Status.ToString(),
CorrelationId = correlationId,
Timestamp = DateTime.UtcNow,
};
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Health check called {@Log}", log);
// Отправляем стандартный ответ
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(
new { status = report.Status.ToString() }
);
},
}
)
.RequireAuthorization("AllowAll");
app.MapHealthChecks(
"/health/ready",
new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var result = new
{
status = report.Status.ToString(),
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString(),
description = e.Value.Description,
}),
};
await context.Response.WriteAsJsonAsync(result);
},
}
)
.RequireAuthorization(["AllowAll"]);
return app;
}
}