diff --git a/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj b/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj index dc2a075..e6db671 100644 --- a/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj +++ b/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj @@ -26,8 +26,9 @@ - - + + + diff --git a/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml b/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml new file mode 100644 index 0000000..623a90c --- /dev/null +++ b/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml @@ -0,0 +1,68 @@ +@page "/health-ui" +@model DigitalData.MessagingService.API.Pages.HealthDashboardModel +@{ + ViewData["Title"] = "Health Dashboard"; + Layout = "_Layout"; +} + + + +Health Dashboard + + + @(Model.OverallStatus == Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus.Healthy ? "?" : "?") + @Model.OverallStatus + + + + Zuletzt geprüft: @Model.CheckedAt.ToString("yyyy-MM-dd HH:mm:ss") UTC + · Aktualisieren + · JSON + + + + @foreach (var entry in Model.Entries) + { + + + @entry.Name + @entry.Status + + @if (!string.IsNullOrWhiteSpace(entry.Description)) + { + @entry.Description + } + @if (entry.Tags.Any()) + { + + @foreach (var tag in entry.Tags) + { + @tag + } + + } + Dauer: @entry.Duration.TotalMilliseconds.ToString("0")ms + + } + diff --git a/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml.cs b/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml.cs new file mode 100644 index 0000000..530f698 --- /dev/null +++ b/src/presentation/DigitalData.MessagingService.API/Pages/HealthDashboard.cshtml.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace DigitalData.MessagingService.API.Pages; + +public class HealthDashboardModel(HealthCheckService healthCheckService) : PageModel +{ + public HealthStatus OverallStatus { get; private set; } + public DateTime CheckedAt { get; private set; } + public IReadOnlyList Entries { get; private set; } = []; + + public async Task OnGetAsync() + { + var report = await healthCheckService.CheckHealthAsync(); + CheckedAt = DateTime.UtcNow; + OverallStatus = report.Status; + Entries = report.Entries + .Select(e => new HealthEntry( + Name: e.Key, + Status: e.Value.Status, + Description: e.Value.Description, + Duration: e.Value.Duration, + Tags: e.Value.Tags)) + .OrderBy(e => e.Status) + .ToList(); + } + + public record HealthEntry( + string Name, + HealthStatus Status, + string? Description, + TimeSpan Duration, + IEnumerable Tags); +} diff --git a/src/presentation/DigitalData.MessagingService.API/Pages/_Layout.cshtml b/src/presentation/DigitalData.MessagingService.API/Pages/_Layout.cshtml new file mode 100644 index 0000000..cbba227 --- /dev/null +++ b/src/presentation/DigitalData.MessagingService.API/Pages/_Layout.cshtml @@ -0,0 +1,28 @@ + + + + + + @ViewData["Title"] — DigitalData MessagingService + + + + + DigitalData MessagingService + Health + Logs + API + + + @RenderBody() + + + diff --git a/src/presentation/DigitalData.MessagingService.API/Pages/_ViewImports.cshtml b/src/presentation/DigitalData.MessagingService.API/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..b697ff5 --- /dev/null +++ b/src/presentation/DigitalData.MessagingService.API/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using DigitalData.MessagingService.API.Pages +@namespace DigitalData.MessagingService.API.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/presentation/DigitalData.MessagingService.API/Program.cs b/src/presentation/DigitalData.MessagingService.API/Program.cs index e1b85b3..cf43e75 100644 --- a/src/presentation/DigitalData.MessagingService.API/Program.cs +++ b/src/presentation/DigitalData.MessagingService.API/Program.cs @@ -1,6 +1,10 @@ using DigitalData.MessagingService.API.Middleware; using DigitalData.MessagingService.Application; using DigitalData.MessagingService.Infrastructure; +using DigitalData.MessagingService.Infrastructure.Services.Background; +using HealthChecks.UI.Client; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; using Serilog; using Serilog.Ui.Core.Extensions; using Serilog.Ui.SqliteDataProvider.Extensions; @@ -66,7 +70,14 @@ try // Register Infrastructure layer (RabbitMQ, Repositories, etc.) builder.Services.AddInfrastructure(builder.Configuration); + // Health checks — each check corresponds to a singleton registered in the Infrastructure layer + builder.Services.AddHealthChecks() + .AddCheck("rabbitmq-consumers", tags: ["rabbitmq", "ready"]) + .AddCheck("rabbitmq-connection", tags: ["rabbitmq", "live"]) + .AddCheck("email-sync-worker", tags: ["workers", "live"]); + builder.Services.AddControllers(); + builder.Services.AddRazorPages(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => @@ -95,6 +106,7 @@ try ## WeiterfĂĽhrende Links - [Serilog Log-Viewer ↗](/serilog-ui) + - [Health Status ↗](/health-ui) """ }); }); @@ -155,6 +167,22 @@ try app.MapControllers(); + // Health check endpoint — raw JSON, used by HealthChecks UI and monitoring tools + // Returns HTTP 503 if any check tagged 'ready' is Unhealthy + app.MapHealthChecks("/health", new HealthCheckOptions + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, + ResultStatusCodes = + { + [HealthStatus.Healthy] = StatusCodes.Status200OK, + [HealthStatus.Degraded] = StatusCodes.Status200OK, + [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable, + } + }); + + app.MapRazorPages(); + Log.Information("MessagingService API started successfully"); app.Run();
+ Zuletzt geprüft: @Model.CheckedAt.ToString("yyyy-MM-dd HH:mm:ss") UTC + · Aktualisieren + · JSON +