QrRapido/Services/HealthChecks/SeqHealthCheck.cs
2025-07-28 18:22:47 -03:00

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