using System.Diagnostics; using JdeScoping.DataAccess.Interfaces; using Microsoft.Extensions.Diagnostics.HealthChecks; namespace JdeScoping.DataAccess.HealthChecks; /// /// Health check for database connectivity to all data sources. /// /// /// Checks connectivity to: /// - LotFinder (SQL Server) - Critical, causes Unhealthy if unavailable /// - JDE, CMS, GIW (Oracle) - Important but not critical, causes Degraded if unavailable /// public class DatabaseHealthCheck : IHealthCheck { private readonly IDbConnectionFactory _connectionFactory; private static readonly TimeSpan ConnectionTimeout = TimeSpan.FromSeconds(5); private static readonly long DegradedThresholdMs = 2000; /// /// Initializes a new instance of the class. /// /// The database connection factory. public DatabaseHealthCheck(IDbConnectionFactory connectionFactory) { _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); } /// public async Task CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default) { var data = new Dictionary(); var unhealthyDbs = new List(); var degradedDbs = new List(); // Check LotFinder (SQL Server) - Critical await CheckLotFinderAsync(data, unhealthyDbs, degradedDbs, cancellationToken); // Check Oracle databases - Important but not critical await CheckJdeAsync(data, unhealthyDbs, degradedDbs, cancellationToken); await CheckCmsAsync(data, unhealthyDbs, degradedDbs, cancellationToken); await CheckGiwAsync(data, unhealthyDbs, degradedDbs, cancellationToken); // Determine overall status if (unhealthyDbs.Count > 0) { return HealthCheckResult.Unhealthy( $"Database(s) unavailable: {string.Join(", ", unhealthyDbs)}", data: data); } if (degradedDbs.Count > 0) { return HealthCheckResult.Degraded( $"Database(s) slow/degraded: {string.Join(", ", degradedDbs)}", data: data); } return HealthCheckResult.Healthy("All databases connected", data: data); } private async Task CheckLotFinderAsync( Dictionary data, List unhealthyDbs, List degradedDbs, CancellationToken cancellationToken) { const string name = "LotFinder"; var sw = Stopwatch.StartNew(); try { using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(ConnectionTimeout); await using var conn = await _connectionFactory.CreateLotFinderConnectionAsync(cts.Token); sw.Stop(); RecordSuccess(name, sw.ElapsedMilliseconds, data, degradedDbs); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { sw.Stop(); RecordTimeout(name, sw.ElapsedMilliseconds, data, unhealthyDbs); } catch (Exception ex) { sw.Stop(); RecordFailure(name, sw.ElapsedMilliseconds, ex.Message, data, unhealthyDbs); } } private async Task CheckJdeAsync( Dictionary data, List unhealthyDbs, List degradedDbs, CancellationToken cancellationToken) { const string name = "JDE"; var sw = Stopwatch.StartNew(); try { using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(ConnectionTimeout); await using var conn = await _connectionFactory.CreateJdeConnectionAsync(cts.Token); sw.Stop(); RecordSuccess(name, sw.ElapsedMilliseconds, data, degradedDbs); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { sw.Stop(); RecordTimeout(name, sw.ElapsedMilliseconds, data, degradedDbs); } catch (Exception ex) { sw.Stop(); RecordFailure(name, sw.ElapsedMilliseconds, ex.Message, data, degradedDbs); } } private async Task CheckCmsAsync( Dictionary data, List unhealthyDbs, List degradedDbs, CancellationToken cancellationToken) { const string name = "CMS"; var sw = Stopwatch.StartNew(); try { using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(ConnectionTimeout); await using var conn = await _connectionFactory.CreateCmsConnectionAsync(cts.Token); sw.Stop(); RecordSuccess(name, sw.ElapsedMilliseconds, data, degradedDbs); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { sw.Stop(); RecordTimeout(name, sw.ElapsedMilliseconds, data, degradedDbs); } catch (Exception ex) { sw.Stop(); RecordFailure(name, sw.ElapsedMilliseconds, ex.Message, data, degradedDbs); } } private async Task CheckGiwAsync( Dictionary data, List unhealthyDbs, List degradedDbs, CancellationToken cancellationToken) { const string name = "GIW"; var sw = Stopwatch.StartNew(); try { using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); cts.CancelAfter(ConnectionTimeout); await using var conn = await _connectionFactory.CreateGiwConnectionAsync(cts.Token); sw.Stop(); RecordSuccess(name, sw.ElapsedMilliseconds, data, degradedDbs); } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { sw.Stop(); RecordTimeout(name, sw.ElapsedMilliseconds, data, degradedDbs); } catch (Exception ex) { sw.Stop(); RecordFailure(name, sw.ElapsedMilliseconds, ex.Message, data, degradedDbs); } } private static void RecordSuccess( string name, long elapsedMs, Dictionary data, List degradedDbs) { data[$"{name}_Status"] = "Connected"; data[$"{name}_ResponseMs"] = elapsedMs; if (elapsedMs > DegradedThresholdMs) { degradedDbs.Add(name); } } private static void RecordTimeout( string name, long elapsedMs, Dictionary data, List problemDbs) { data[$"{name}_Status"] = "Timeout"; data[$"{name}_ResponseMs"] = elapsedMs; data[$"{name}_Error"] = $"Connection timeout after {ConnectionTimeout.TotalSeconds}s"; problemDbs.Add(name); } private static void RecordFailure( string name, long elapsedMs, string errorMessage, Dictionary data, List problemDbs) { data[$"{name}_Status"] = "Failed"; data[$"{name}_ResponseMs"] = elapsedMs; data[$"{name}_Error"] = errorMessage; problemDbs.Add(name); } }