QrRapido/Controllers/HealthController.cs
Ricardo Carneiro 5ba0d62595
All checks were successful
Deploy QR Rapido / test (push) Successful in 3m51s
Deploy QR Rapido / build-and-push (push) Successful in 14m7s
Deploy QR Rapido / deploy-staging (push) Has been skipped
Deploy QR Rapido / deploy-production (push) Successful in 1m41s
fix: ajustes diversos
2025-09-20 22:46:08 -03:00

331 lines
13 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Diagnostics;
using System.Text.Json;
using System.Threading;
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 = System.Diagnostics.Process.GetCurrentProcess().StartTime.ToUniversalTime();
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"),
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>
/// 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 uptimeMinutes = (DateTime.UtcNow - _startTime).TotalMinutes;
var memoryBytes = GC.GetTotalMemory(false);
var memoryMB = memoryBytes / 1024.0 / 1024.0;
var response = new
{
status = overallStatus,
application = _applicationName,
version = _version,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"),
uptime = System.FormattableString.Invariant($"{(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"
}
};
_logger.LogInformation("[HEALTH] Memory: {memoryBytes} MemoryMB: {memoryMB} ThreadPool: {threadPool} ProcessId: {processId} ActiveConnections: {activeConnections} UptimeMinutes: {uptimeMinutes}",
memoryBytes,
memoryMB,
ThreadPool.ThreadCount,
Process.GetCurrentProcess().Id,
HttpContext.Connection?.Id ?? "null",
Math.Round(uptimeMinutes, 1));
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";
}
}
}