From 5b1bd1f1e8300e9fc0065118fe436173068872bd Mon Sep 17 00:00:00 2001 From: Arne Dumarey Date: Tue, 15 Oct 2024 10:29:52 +0200 Subject: [PATCH] refactor(backoffice): projections healthcheck --- paket.dependencies | 2 +- paket.lock | 2 +- .../HealthCheckRunner.cs | 55 ++++++++++++++++++ .../Program.cs | 18 +++++- .../ProjectionsHealthCheckRunner.cs | 56 ------------------- .../paket.references | 2 + 6 files changed, 76 insertions(+), 59 deletions(-) create mode 100644 src/ParcelRegistry.Projections.BackOffice/HealthCheckRunner.cs delete mode 100644 src/ParcelRegistry.Projections.BackOffice/ProjectionsHealthCheckRunner.cs diff --git a/paket.dependencies b/paket.dependencies index e1ce2516..bb0b0aed 100755 --- a/paket.dependencies +++ b/paket.dependencies @@ -75,7 +75,7 @@ nuget Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector.Testing 14.0.0 nuget Be.Vlaanderen.Basisregisters.ProjectionHandling.Testing.Xunit 14.0.0 nuget Be.Vlaanderen.Basisregisters.ProjectionHandling.Syndication 14.0.0 -nuget Be.Vlaanderen.Basisregisters.Projector 15.0.0 +nuget Be.Vlaanderen.Basisregisters.Projector 15.1.0 nuget Be.Vlaanderen.Basisregisters.Crab 4.0.0 diff --git a/paket.lock b/paket.lock index 6c118ed6..9a62420c 100644 --- a/paket.lock +++ b/paket.lock @@ -380,7 +380,7 @@ NUGET Microsoft.EntityFrameworkCore (>= 8.0.2) Microsoft.Extensions.Logging (>= 8.0) xunit (>= 2.7) - Be.Vlaanderen.Basisregisters.Projector (15.0) + Be.Vlaanderen.Basisregisters.Projector (15.1) Autofac (>= 8.0) Autofac.Extensions.DependencyInjection (>= 9.0) Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector (>= 14.0) diff --git a/src/ParcelRegistry.Projections.BackOffice/HealthCheckRunner.cs b/src/ParcelRegistry.Projections.BackOffice/HealthCheckRunner.cs new file mode 100644 index 00000000..877bef52 --- /dev/null +++ b/src/ParcelRegistry.Projections.BackOffice/HealthCheckRunner.cs @@ -0,0 +1,55 @@ +namespace ParcelRegistry.Projections.BackOffice +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.Diagnostics.HealthChecks; + using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Logging; + + public sealed class HealthCheckRunner : BackgroundService + { + private readonly HealthCheckService _healthCheckService; + private readonly IHostApplicationLifetime _hostApplicationLifetime; + private readonly ILogger _logger; + private readonly TimeSpan _checkInterval = TimeSpan.FromSeconds(5); // Check every 5 minutes + + public HealthCheckRunner( + HealthCheckService healthCheckService, + IHostApplicationLifetime hostApplicationLifetime, + ILogger logger) + { + _healthCheckService = healthCheckService; + _hostApplicationLifetime = hostApplicationLifetime; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken).ConfigureAwait(false); + + while (!stoppingToken.IsCancellationRequested) + { + var report = await _healthCheckService.CheckHealthAsync(stoppingToken); + + // Log the health check result + if (report.Status == HealthStatus.Healthy) + { + _logger.LogInformation("Database health check passed."); + } + else + { + _logger.LogError("Database health check failed. Stopping application."); + foreach (var entry in report.Entries) + { + _logger.LogError($"{entry.Key}: {entry.Value.Status} - {entry.Value.Description}"); + } + + _hostApplicationLifetime.StopApplication(); + } + + await Task.Delay(_checkInterval, stoppingToken); + } + } + } +} diff --git a/src/ParcelRegistry.Projections.BackOffice/Program.cs b/src/ParcelRegistry.Projections.BackOffice/Program.cs index 95921b12..a11bfae6 100644 --- a/src/ParcelRegistry.Projections.BackOffice/Program.cs +++ b/src/ParcelRegistry.Projections.BackOffice/Program.cs @@ -19,6 +19,7 @@ namespace ParcelRegistry.Projections.BackOffice using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ParcelRegistry.Infrastructure; @@ -86,7 +87,22 @@ public static async Task Main(string[] args) .MigrationsHistoryTable(MigrationTables.BackOffice, Schema.BackOffice) )) .AddHostedService() - .AddHostedService(); + .AddHostedService(); + + foreach (var connectionString in hostContext.Configuration.GetSection("ConnectionStrings").GetChildren()) + { + services.AddHealthChecks() + .AddSqlServer( + connectionString.Value, + name: $"sqlserver-{connectionString.Key.ToLowerInvariant()}", + tags: ["db", "sql", "sqlserver"]); + } + + services.AddHealthChecks() + .AddDbContextCheck( + $"dbcontext-{nameof(BackOfficeContext).ToLowerInvariant()}", + tags: ["db", "sql", "sqlserver"]) + .AddCheck("projections", failureStatus: HealthStatus.Unhealthy, tags: ["projections"]); }) .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureContainer((hostContext, builder) => diff --git a/src/ParcelRegistry.Projections.BackOffice/ProjectionsHealthCheckRunner.cs b/src/ParcelRegistry.Projections.BackOffice/ProjectionsHealthCheckRunner.cs deleted file mode 100644 index 96e4b4cb..00000000 --- a/src/ParcelRegistry.Projections.BackOffice/ProjectionsHealthCheckRunner.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace ParcelRegistry.Projections.BackOffice -{ - using System; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using Be.Vlaanderen.Basisregisters.Projector.ConnectedProjections; - using Microsoft.Extensions.Hosting; - using Microsoft.Extensions.Logging; - - public sealed class ProjectionsHealthCheckRunner : BackgroundService - { - private static readonly TimeSpan Interval = TimeSpan.FromSeconds(5); - - private readonly IConnectedProjectionsManager _projectionsManager; - private readonly IHostApplicationLifetime _hostApplicationLifetime; - private readonly ILogger _logger; - - private Timer? _timer; - - public ProjectionsHealthCheckRunner( - IConnectedProjectionsManager projectionsManager, - IHostApplicationLifetime hostApplicationLifetime, - ILoggerFactory logger) - { - _projectionsManager = projectionsManager; - _hostApplicationLifetime = hostApplicationLifetime; - _logger = logger.CreateLogger(); - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - await Task.Delay(1000 * 60 * 5, stoppingToken).ConfigureAwait(false); - - _logger.LogInformation("Projections health check starting"); - - _timer = new Timer(RunHealthCheck, null, TimeSpan.Zero, Interval); - - _logger.LogInformation("Projections health check started"); - } - - private void RunHealthCheck(object? state) - { - var registeredProjections = - _projectionsManager.GetRegisteredProjections(); - - if (registeredProjections - .Any(x => x.State == ConnectedProjectionState.Stopped)) - { - _logger.LogInformation("Projections with status Stopped detected. Shutting down service."); - - _hostApplicationLifetime.StopApplication(); - } - } - } -} diff --git a/src/ParcelRegistry.Projections.BackOffice/paket.references b/src/ParcelRegistry.Projections.BackOffice/paket.references index 5e4abc10..636d07b7 100644 --- a/src/ParcelRegistry.Projections.BackOffice/paket.references +++ b/src/ParcelRegistry.Projections.BackOffice/paket.references @@ -3,6 +3,8 @@ Be.Vlaanderen.Basisregisters.ProjectionHandling.SqlStreamStore.Autofac Be.Vlaanderen.Basisregisters.ProjectionHandling.Runner.SqlServer Be.Vlaanderen.Basisregisters.Projector +AspNetCore.HealthChecks.SqlServer + Datadog.Trace.Bundle SourceLink.Embed.AllSourceFiles