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);
}
}