126 lines
5.5 KiB
C#
126 lines
5.5 KiB
C#
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
|
using System.Diagnostics;
|
|
using System.Text;
|
|
|
|
namespace QRRapidoApp.Services.HealthChecks
|
|
{
|
|
public class SeqHealthCheck : IHealthCheck
|
|
{
|
|
private readonly IConfiguration _configuration;
|
|
private readonly ILogger<SeqHealthCheck> _logger;
|
|
private readonly HttpClient _httpClient;
|
|
|
|
private readonly int _timeoutSeconds;
|
|
private readonly string _testLogMessage;
|
|
|
|
public SeqHealthCheck(
|
|
IConfiguration configuration,
|
|
ILogger<SeqHealthCheck> logger,
|
|
IHttpClientFactory httpClientFactory)
|
|
{
|
|
_configuration = configuration;
|
|
_logger = logger;
|
|
_httpClient = httpClientFactory.CreateClient();
|
|
|
|
_timeoutSeconds = configuration.GetValue<int>("HealthChecks:Seq:TimeoutSeconds", 3);
|
|
_testLogMessage = configuration.GetValue<string>("HealthChecks:Seq:TestLogMessage", "QRRapido health check test");
|
|
}
|
|
|
|
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
|
{
|
|
var stopwatch = Stopwatch.StartNew();
|
|
var data = new Dictionary<string, object>();
|
|
|
|
var seqUrl = _configuration["Serilog:SeqUrl"];
|
|
if (string.IsNullOrEmpty(seqUrl))
|
|
{
|
|
return HealthCheckResult.Degraded("Seq URL not configured - logging to console only", data: data);
|
|
}
|
|
|
|
try
|
|
{
|
|
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(_timeoutSeconds));
|
|
using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
|
|
|
|
// Test basic connectivity to Seq server
|
|
var pingUrl = $"{seqUrl.TrimEnd('/')}/api";
|
|
var response = await _httpClient.GetAsync(pingUrl, combinedCts.Token);
|
|
|
|
var latencyMs = stopwatch.ElapsedMilliseconds;
|
|
data["reachable"] = response.IsSuccessStatusCode;
|
|
data["latency"] = $"{latencyMs}ms";
|
|
data["seqUrl"] = seqUrl;
|
|
data["statusCode"] = (int)response.StatusCode;
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
data["error"] = $"HTTP {response.StatusCode}";
|
|
return HealthCheckResult.Unhealthy($"Seq server not reachable at {seqUrl} (HTTP {response.StatusCode})", data: data);
|
|
}
|
|
|
|
// Try to send a test log message if we can access the raw events endpoint
|
|
try
|
|
{
|
|
await SendTestLogAsync(seqUrl, combinedCts.Token);
|
|
data["lastLog"] = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
|
|
data["testLogSent"] = true;
|
|
}
|
|
catch (Exception logEx)
|
|
{
|
|
_logger.LogWarning(logEx, "Failed to send test log to Seq during health check");
|
|
data["testLogSent"] = false;
|
|
data["testLogError"] = logEx.Message;
|
|
}
|
|
|
|
stopwatch.Stop();
|
|
data["totalCheckTimeMs"] = stopwatch.ElapsedMilliseconds;
|
|
|
|
// Determine health status
|
|
if (latencyMs > 2000)
|
|
{
|
|
return HealthCheckResult.Degraded($"Seq responding slowly ({latencyMs}ms)", data: data);
|
|
}
|
|
|
|
return HealthCheckResult.Healthy($"Seq healthy ({latencyMs}ms)", data: data);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
data["reachable"] = false;
|
|
data["error"] = "timeout";
|
|
return HealthCheckResult.Unhealthy($"Seq health check timed out after {_timeoutSeconds} seconds", data: data);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Seq health check failed");
|
|
data["reachable"] = false;
|
|
data["error"] = ex.Message;
|
|
return HealthCheckResult.Unhealthy($"Seq health check failed: {ex.Message}", data: data);
|
|
}
|
|
}
|
|
|
|
private async Task SendTestLogAsync(string seqUrl, CancellationToken cancellationToken)
|
|
{
|
|
var apiKey = _configuration["Serilog:ApiKey"];
|
|
var eventsUrl = $"{seqUrl.TrimEnd('/')}/api/events/raw";
|
|
|
|
// Create a simple CLEF (Compact Log Event Format) message
|
|
var timestamp = DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffK");
|
|
var logEntry = $"{{\"@t\":\"{timestamp}\",\"@l\":\"Information\",\"@m\":\"Health check test from QRRapido\",\"ApplicationName\":\"QRRapido\",\"HealthCheck\":true,\"TestMessage\":\"{_testLogMessage}\"}}";
|
|
|
|
var content = new StringContent(logEntry, Encoding.UTF8, "application/vnd.serilog.clef");
|
|
|
|
// Add API key if configured
|
|
if (!string.IsNullOrEmpty(apiKey))
|
|
{
|
|
content.Headers.Add("X-Seq-ApiKey", apiKey);
|
|
}
|
|
|
|
var response = await _httpClient.PostAsync(eventsUrl, content, cancellationToken);
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
throw new HttpRequestException($"Failed to send test log to Seq: HTTP {response.StatusCode}");
|
|
}
|
|
}
|
|
}
|
|
} |