diff --git a/Core.Thalos.DAL.API/HealthCheck/MongoConnectionHealthCheck.cs b/Core.Thalos.DAL.API/HealthCheck/MongoConnectionHealthCheck.cs new file mode 100644 index 0000000..8f5a417 --- /dev/null +++ b/Core.Thalos.DAL.API/HealthCheck/MongoConnectionHealthCheck.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Core.Thalos.DAL.API.HealthCheck +{ + public class MongoConnectionHealthCheck(string connectionString, string databaseName) : IHealthCheck + { + + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + var settings = MongoClientSettings.FromConnectionString(connectionString); + + try + { + var mongoClient = new MongoClient(settings); + + var database = mongoClient.GetDatabase(databaseName); + var command = new BsonDocument("ping", 1); + + await database.RunCommandAsync(command); + + return HealthCheckResult.Healthy($"MongoDB is healthy, {databaseName} database from {settings.Server} is reachable."); + } + catch (Exception ex) + { + return HealthCheckResult.Degraded($"MongoDB is Degraded, {databaseName} database from {settings?.Server?.Host} is unreachable.", ex); + } + } + } +} diff --git a/Core.Thalos.DAL.API/HealthCheck/RedisConnectionHealthCheck.cs b/Core.Thalos.DAL.API/HealthCheck/RedisConnectionHealthCheck.cs new file mode 100644 index 0000000..ab322e5 --- /dev/null +++ b/Core.Thalos.DAL.API/HealthCheck/RedisConnectionHealthCheck.cs @@ -0,0 +1,30 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; +using StackExchange.Redis; + +public sealed class RedisConnectionHealthCheck : IHealthCheck +{ + private readonly string _connectionString; + public RedisConnectionHealthCheck(string connectionString) => _connectionString = connectionString; + + public async Task CheckHealthAsync( + HealthCheckContext context, + CancellationToken cancellationToken = default) + { + try + { + var options = ConfigurationOptions.Parse(_connectionString); + options.AbortOnConnectFail = false; + options.ConnectTimeout = 2000; // optional, be snappy + + using var mux = await ConnectionMultiplexer.ConnectAsync(options); + if (!mux.IsConnected) return HealthCheckResult.Unhealthy("Redis not connected."); + + var ping = await mux.GetDatabase().PingAsync(); + return HealthCheckResult.Healthy($"Redis OK (ping {ping.TotalMilliseconds:N0} ms)"); + } + catch (Exception ex) + { + return HealthCheckResult.Unhealthy("Redis check failed.", ex); + } + } +} diff --git a/Core.Thalos.DAL.API/HealthCheck/Writer/HealthCheckResponseWriter.cs b/Core.Thalos.DAL.API/HealthCheck/Writer/HealthCheckResponseWriter.cs new file mode 100644 index 0000000..ab6e8fd --- /dev/null +++ b/Core.Thalos.DAL.API/HealthCheck/Writer/HealthCheckResponseWriter.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; +using System.Text.Json; + +namespace Core.Thalos.DAL.API.HealthCheck.Writer +{ + public static class HealthCheckResponseWriter + { + public static Task WriteResponse(HttpContext context, HealthReport result) + { + context.Response.ContentType = "application/json"; + + context.Response.StatusCode = result.Status switch + { + HealthStatus.Healthy => StatusCodes.Status200OK, + HealthStatus.Degraded => StatusCodes.Status500InternalServerError, + HealthStatus.Unhealthy => StatusCodes.Status503ServiceUnavailable, + _ => StatusCodes.Status500InternalServerError + }; + + var options = new JsonSerializerOptions + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + var json = new + { + status = result.Status.ToString(), + services = result.Entries.Select(e => new + { + key = e.Key, + status = e.Value.Status.ToString(), + description = e.Value.Description ?? string.Empty, + exception = e.Value.Exception?.Message, + duration = e.Value.Duration.ToString(), + + statusCode = e.Value.Status switch + { + HealthStatus.Healthy => StatusCodes.Status200OK, + HealthStatus.Degraded => StatusCodes.Status500InternalServerError, + HealthStatus.Unhealthy => StatusCodes.Status503ServiceUnavailable, + _ => StatusCodes.Status500InternalServerError + } + }) + }; + + return context.Response.WriteAsync(JsonSerializer.Serialize(json, options)); + } + } +} diff --git a/Core.Thalos.DAL.API/Program.cs b/Core.Thalos.DAL.API/Program.cs index 8c1489d..71b7d7a 100644 --- a/Core.Thalos.DAL.API/Program.cs +++ b/Core.Thalos.DAL.API/Program.cs @@ -4,8 +4,12 @@ using Core.Blueprint.Logging.Configuration; using Core.Blueprint.Redis.Configuration; using Core.Thalos.BuildingBlocks; using Core.Thalos.BuildingBlocks.Configuration; +using Core.Thalos.DAL.API.HealthCheck; +using Core.Thalos.DAL.API.HealthCheck.Writer; using Core.Thalos.Provider; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.HttpLogging; +using Microsoft.Extensions.Diagnostics.HealthChecks; using System.Reflection; using System.Text.Json.Serialization; @@ -64,6 +68,21 @@ builder.Host.ConfigureServices((context, services) => }); }); +// Add health checks +builder.Services.AddHealthChecks() + .AddCheck( + "mongodb", + new MongoConnectionHealthCheck( + connectionString: builder.Configuration.GetConnectionString("MongoDB")!, + databaseName: builder.Configuration.GetSection("MongoDb:DatabaseName").Value!), + failureStatus: HealthStatus.Unhealthy, + tags: new[] { "db", "mongo" }) + .AddCheck( + "redis", + new RedisConnectionHealthCheck(builder.Configuration.GetConnectionString("Redis")!), + failureStatus: HealthStatus.Unhealthy, + tags: new[] { "db", "redis" }); + var app = builder.Build(); app.UseLogging(builder.Configuration); @@ -82,6 +101,10 @@ app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); -app.MapHealthChecks("/health"); +app.MapHealthChecks("/health", new HealthCheckOptions +{ + ResponseWriter = HealthCheckResponseWriter.WriteResponse, + AllowCachingResponses = false +}); app.Run(); \ No newline at end of file