QrRapido/Controllers/HealthController.cs
2025-07-28 18:22:47 -03:00

348 lines
13 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Diagnostics;
using System.Text.Json;
namespace QRRapidoApp.Controllers
{
[ApiController]
[Route("health")]
public class HealthController : ControllerBase
{
private readonly HealthCheckService _healthCheckService;
private readonly IConfiguration _configuration;
private readonly ILogger<HealthController> _logger;
private readonly string _applicationName;
private readonly string _version;
private static readonly DateTime _startTime = DateTime.UtcNow;
public HealthController(
HealthCheckService healthCheckService,
IConfiguration configuration,
ILogger<HealthController> logger)
{
_healthCheckService = healthCheckService;
_configuration = configuration;
_logger = logger;
_applicationName = configuration["ApplicationName"] ?? "QRRapido";
_version = configuration["App:Version"] ?? "1.0.0";
}
/// <summary>
/// Comprehensive health check with detailed information
/// GET /health/detailed
/// </summary>
[HttpGet("detailed")]
public async Task<IActionResult> GetDetailedHealth()
{
try
{
var stopwatch = Stopwatch.StartNew();
var healthReport = await _healthCheckService.CheckHealthAsync();
stopwatch.Stop();
var uptime = DateTime.UtcNow - _startTime;
var overallStatus = DetermineOverallStatus(healthReport.Status);
var response = new
{
applicationName = _applicationName,
status = overallStatus,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
uptime = $"{uptime.Days}d {uptime.Hours}h {uptime.Minutes}m",
totalDuration = $"{stopwatch.ElapsedMilliseconds}ms",
checks = new
{
mongodb = ExtractCheckResult(healthReport, "mongodb"),
seq = ExtractCheckResult(healthReport, "seq"),
resources = ExtractCheckResult(healthReport, "resources"),
externalServices = ExtractCheckResult(healthReport, "external_services")
},
version = _version,
environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"
};
var statusCode = overallStatus switch
{
"unhealthy" => 503,
"degraded" => 200, // Still return 200 for degraded to avoid false alarms
_ => 200
};
_logger.LogInformation("Detailed health check completed - Status: {Status}, Duration: {Duration}ms",
overallStatus, stopwatch.ElapsedMilliseconds);
return StatusCode(statusCode, response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Detailed health check failed");
return StatusCode(503, new
{
applicationName = _applicationName,
status = "unhealthy",
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
error = "Health check system failure",
version = _version
});
}
}
/// <summary>
/// MongoDB-specific health check
/// GET /health/mongodb
/// </summary>
[HttpGet("mongodb")]
public async Task<IActionResult> GetMongoDbHealth()
{
try
{
var healthReport = await _healthCheckService.CheckHealthAsync(check => check.Name == "mongodb");
var mongoCheck = healthReport.Entries.FirstOrDefault(e => e.Key == "mongodb");
if (mongoCheck.Key == null)
{
return StatusCode(503, new { status = "unhealthy", error = "MongoDB health check not found" });
}
var statusCode = mongoCheck.Value.Status == HealthStatus.Healthy ? 200 : 503;
var response = ExtractCheckResult(healthReport, "mongodb");
return StatusCode(statusCode, response);
}
catch (Exception ex)
{
_logger.LogError(ex, "MongoDB health check failed");
return StatusCode(503, new { status = "unhealthy", error = ex.Message });
}
}
/// <summary>
/// Seq logging service health check
/// GET /health/seq
/// </summary>
[HttpGet("seq")]
public async Task<IActionResult> GetSeqHealth()
{
try
{
var healthReport = await _healthCheckService.CheckHealthAsync(check => check.Name == "seq");
var seqCheck = healthReport.Entries.FirstOrDefault(e => e.Key == "seq");
if (seqCheck.Key == null)
{
return StatusCode(503, new { status = "unhealthy", error = "Seq health check not found" });
}
var statusCode = seqCheck.Value.Status == HealthStatus.Unhealthy ? 503 : 200;
var response = ExtractCheckResult(healthReport, "seq");
return StatusCode(statusCode, response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Seq health check failed");
return StatusCode(503, new { status = "unhealthy", error = ex.Message });
}
}
/// <summary>
/// System resources health check
/// GET /health/resources
/// </summary>
[HttpGet("resources")]
public async Task<IActionResult> GetResourcesHealth()
{
try
{
var healthReport = await _healthCheckService.CheckHealthAsync(check => check.Name == "resources");
var resourceCheck = healthReport.Entries.FirstOrDefault(e => e.Key == "resources");
if (resourceCheck.Key == null)
{
return StatusCode(503, new { status = "unhealthy", error = "Resources health check not found" });
}
var statusCode = resourceCheck.Value.Status == HealthStatus.Unhealthy ? 503 : 200;
var response = ExtractCheckResult(healthReport, "resources");
return StatusCode(statusCode, response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Resources health check failed");
return StatusCode(503, new { status = "unhealthy", error = ex.Message });
}
}
/// <summary>
/// External services health check
/// GET /health/external
/// </summary>
[HttpGet("external")]
public async Task<IActionResult> GetExternalServicesHealth()
{
try
{
var healthReport = await _healthCheckService.CheckHealthAsync(check => check.Name == "external_services");
var externalCheck = healthReport.Entries.FirstOrDefault(e => e.Key == "external_services");
if (externalCheck.Key == null)
{
return StatusCode(503, new { status = "unhealthy", error = "External services health check not found" });
}
var statusCode = externalCheck.Value.Status == HealthStatus.Unhealthy ? 503 : 200;
var response = ExtractCheckResult(healthReport, "external_services");
return StatusCode(statusCode, response);
}
catch (Exception ex)
{
_logger.LogError(ex, "External services health check failed");
return StatusCode(503, new { status = "unhealthy", error = ex.Message });
}
}
/// <summary>
/// Simple health check - just overall status
/// GET /health/simple or GET /health
/// </summary>
[HttpGet("simple")]
[HttpGet("")]
public async Task<IActionResult> GetSimpleHealth()
{
try
{
var healthReport = await _healthCheckService.CheckHealthAsync();
var overallStatus = DetermineOverallStatus(healthReport.Status);
var statusCode = overallStatus switch
{
"unhealthy" => 503,
_ => 200
};
var response = new
{
status = overallStatus,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
};
return StatusCode(statusCode, response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Simple health check failed");
return StatusCode(503, new
{
status = "unhealthy",
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
error = ex.Message
});
}
}
/// <summary>
/// Health check for Uptime Kuma monitoring
/// GET /health/uptime-kuma
/// </summary>
[HttpGet("uptime-kuma")]
public async Task<IActionResult> GetUptimeKumaHealth()
{
try
{
var healthReport = await _healthCheckService.CheckHealthAsync();
var overallStatus = DetermineOverallStatus(healthReport.Status);
// For Uptime Kuma, we want to return 200 OK for healthy/degraded and 503 for unhealthy
var statusCode = overallStatus == "unhealthy" ? 503 : 200;
var response = new
{
status = overallStatus,
application = _applicationName,
version = _version,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
uptime = $"{(DateTime.UtcNow - _startTime).TotalHours:F1}h",
// Include critical metrics for monitoring
metrics = new
{
mongodb_connected = GetCheckStatus(healthReport, "mongodb") != "unhealthy",
seq_reachable = GetCheckStatus(healthReport, "seq") != "unhealthy",
resources_ok = GetCheckStatus(healthReport, "resources") != "unhealthy",
external_services_ok = GetCheckStatus(healthReport, "external_services") != "unhealthy"
}
};
return StatusCode(statusCode, response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Uptime Kuma health check failed");
return StatusCode(503, new
{
status = "unhealthy",
application = _applicationName,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
error = "Health check system failure"
});
}
}
private object ExtractCheckResult(HealthReport healthReport, string checkName)
{
if (!healthReport.Entries.TryGetValue(checkName, out var entry))
{
return new
{
status = "not_configured",
error = $"Health check '{checkName}' not found"
};
}
var baseResult = new Dictionary<string, object>
{
["status"] = entry.Status.ToString().ToLower(),
["duration"] = $"{entry.Duration.TotalMilliseconds}ms",
["description"] = entry.Description ?? ""
};
// Add all custom data from the health check
if (entry.Data != null)
{
foreach (var kvp in entry.Data)
{
baseResult[kvp.Key] = kvp.Value;
}
}
if (entry.Exception != null)
{
baseResult["error"] = entry.Exception.Message;
}
return baseResult;
}
private string DetermineOverallStatus(HealthStatus healthStatus)
{
return healthStatus switch
{
HealthStatus.Healthy => "healthy",
HealthStatus.Degraded => "degraded",
HealthStatus.Unhealthy => "unhealthy",
_ => "unknown"
};
}
private string GetCheckStatus(HealthReport healthReport, string checkName)
{
if (healthReport.Entries.TryGetValue(checkName, out var entry))
{
return entry.Status.ToString().ToLower();
}
return "not_configured";
}
}
}