using ItWorx.KnowledgeHub.Application.Operations; namespace ItWorx.KnowledgeHub.Api.BackgroundServices; public sealed partial class OperationalTelemetryBackgroundService( IServiceScopeFactory scopeFactory, PlatformMetrics metrics, TimeProvider clock, ILogger logger) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { // Do not race initial database migration during host startup. await Task.Delay(TimeSpan.FromSeconds(30), clock, stoppingToken); using var scope = scopeFactory.CreateScope(); var snapshot = await scope.ServiceProvider.GetRequiredService() .ReadAsync(stoppingToken); var now = clock.GetUtcNow(); metrics.SetQueueState(snapshot.QueuedJobs, snapshot.RunningJobs, snapshot.FailedJobs, Age(snapshot.OldestQueuedJobAt, now)); metrics.SetOperationalAges(Age(snapshot.OldestPendingIngestionAt, now), Age(snapshot.LatestInventoryCaptureAt, now), Age(snapshot.LatestVerifiedBackupAt, now)); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; } catch (Exception) { // Omit stale gauges after a failed read, rather than exporting old success. metrics.ClearSnapshots(); LogReadFailed(); } } } private static double? Age(DateTimeOffset? value, DateTimeOffset now) => value is { } time && time <= now ? (now - time).TotalSeconds : null; [LoggerMessage(Level = LogLevel.Warning, Message = "Operationele metriekmeting mislukt; waarden blijven onbekend tot de volgende geslaagde meting.")] private partial void LogReadFailed(); }