169 lines
6.5 KiB
C#
169 lines
6.5 KiB
C#
//Elecciones.Worker/Program.cs
|
|
using Elecciones.Database;
|
|
using Elecciones.Infrastructure.Services;
|
|
using Elecciones.Worker;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Net;
|
|
using Serilog;
|
|
using System.Net.Security;
|
|
using System.Security.Authentication;
|
|
using Polly;
|
|
using Polly.Extensions.Http;
|
|
using System.Threading.RateLimiting;
|
|
|
|
var builder = Host.CreateApplicationBuilder(args);
|
|
|
|
// 1. Registra el servicio del interruptor.
|
|
builder.Services.AddSingleton<LoggingSwitchService>();
|
|
|
|
// 2. Configura Serilog usando UseSerilog en el HostBuilder.
|
|
// (Nota: AddSerilog también funciona, pero UseSerilog es más consistente con la API)
|
|
builder.Services.AddSerilog((services, configuration) => {
|
|
var loggingSwitch = services.GetRequiredService<LoggingSwitchService>();
|
|
configuration
|
|
.ReadFrom.Configuration(builder.Configuration)
|
|
.ReadFrom.Services(services)
|
|
.Enrich.FromLogContext()
|
|
.MinimumLevel.ControlledBy(loggingSwitch.LevelSwitch)
|
|
.WriteTo.Console()
|
|
.WriteTo.File("logs/worker-.log", rollingInterval: RollingInterval.Day);
|
|
});
|
|
|
|
// --- Configuración de Servicios ---
|
|
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
|
builder.Services.AddDbContext<EleccionesDbContext>(options =>
|
|
options.UseSqlServer(connectionString));
|
|
|
|
builder.Services.AddHttpClient("ElectoralApiClient", client =>
|
|
{
|
|
var baseUrl = builder.Configuration["ElectoralApi:BaseUrl"];
|
|
if (!string.IsNullOrEmpty(baseUrl))
|
|
{
|
|
client.BaseAddress = new Uri(baseUrl);
|
|
}
|
|
|
|
// --- TIMEOUT MÁS LARGO ---
|
|
// Aumentamos el tiempo de espera a 90 segundos.
|
|
// Esto le dará a las peticiones lentas de la API tiempo suficiente para responder.
|
|
client.Timeout = TimeSpan.FromSeconds(90);
|
|
|
|
client.DefaultRequestHeaders.Clear();
|
|
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
|
|
client.DefaultRequestHeaders.Add("Accept", "*/*");
|
|
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
|
|
client.DefaultRequestHeaders.Add("Connection", "keep-alive");
|
|
|
|
})
|
|
.ConfigurePrimaryHttpMessageHandler(() =>
|
|
{
|
|
var handler = new SocketsHttpHandler
|
|
{
|
|
// Habilita la descompresión automática de respuestas GZIP y Deflate.
|
|
// Esto soluciona el error de JsonException ('0x1F').
|
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
|
|
|
|
SslOptions = new SslClientAuthenticationOptions
|
|
{
|
|
EnabledSslProtocols = SslProtocols.Tls13,
|
|
}
|
|
};
|
|
|
|
if (!OperatingSystem.IsWindows())
|
|
{
|
|
handler.SslOptions.CipherSuitesPolicy = new CipherSuitesPolicy(new[]
|
|
{
|
|
TlsCipherSuite.TLS_AES_128_GCM_SHA256,
|
|
TlsCipherSuite.TLS_AES_256_GCM_SHA384,
|
|
TlsCipherSuite.TLS_CHACHA20_POLY1305_SHA256
|
|
});
|
|
}
|
|
|
|
return handler;
|
|
})
|
|
|
|
.AddPolicyHandler(GetRetryPolicy());
|
|
|
|
// --- LIMITADOR DE VELOCIDAD BASADO EN TOKEN BUCKET ---
|
|
builder.Services.AddSingleton<RateLimiter>(sp =>
|
|
new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
|
|
{
|
|
TokenLimit = 50, // Ráfaga máxima permitida
|
|
ReplenishmentPeriod = TimeSpan.FromSeconds(1),
|
|
TokensPerPeriod = 20, // Ritmo de recarga: 20 peticiones por segundo
|
|
QueueLimit = 1000,
|
|
QueueProcessingOrder = QueueProcessingOrder.OldestFirst
|
|
}));
|
|
|
|
builder.Services.AddScoped<IElectoralApiService, ElectoralApiService>();
|
|
|
|
// Registramos el servicio de token como un Singleton para que sea compartido.
|
|
builder.Services.AddSingleton<SharedTokenService>();
|
|
// Registramos el servicio de configuraciones de workers como un Singleton para que sea compartido.
|
|
builder.Services.AddSingleton<WorkerConfigService>();
|
|
|
|
// Registramos ambos workers. El framework se encargará de iniciarlos y detenerlos.
|
|
builder.Services.AddHostedService<CriticalDataWorker>();
|
|
builder.Services.AddHostedService<LowPriorityDataWorker>();
|
|
|
|
var host = builder.Build();
|
|
|
|
// La lógica para leer el nivel de logging se ejecuta DESPUÉS de construir el host.
|
|
using (var scope = host.Services.CreateScope())
|
|
{
|
|
var services = scope.ServiceProvider;
|
|
try
|
|
{
|
|
var dbContext = services.GetRequiredService<EleccionesDbContext>();
|
|
var loggingSwitchService = services.GetRequiredService<LoggingSwitchService>();
|
|
|
|
var logLevelConfig = await dbContext.Configuraciones
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(c => c.Clave == "Logging_Level");
|
|
|
|
if (logLevelConfig != null)
|
|
{
|
|
loggingSwitchService.SetLoggingLevel(logLevelConfig.Valor);
|
|
// Obtenemos un logger de la forma correcta para registrar este evento
|
|
var logger = services.GetRequiredService<ILogger<Program>>();
|
|
logger.LogInformation("Nivel de logging inicial establecido desde la BD a: {Level}", logLevelConfig.Valor);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var logger = services.GetRequiredService<ILogger<Program>>();
|
|
logger.LogError(ex, "No se pudo establecer el nivel de logging desde la BD. Usando el nivel por defecto.");
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
// Usamos el logger del host para el mensaje de inicio
|
|
var logger = host.Services.GetRequiredService<ILogger<Program>>();
|
|
logger.LogInformation("Iniciando Elecciones.Worker Host...");
|
|
host.Run();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// En caso de un error fatal, el logger estático (si está configurado) puede ser un respaldo
|
|
Log.Fatal(ex, "El Host de Elecciones.Worker terminó inesperadamente");
|
|
}
|
|
finally
|
|
{
|
|
Log.CloseAndFlush();
|
|
}
|
|
|
|
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
|
|
{
|
|
return HttpPolicyExtensions
|
|
// Manejar peticiones que fallaron por errores de red O que devolvieron un error de servidor (como 504)
|
|
.HandleTransientHttpError()
|
|
// O que devolvieron un código 504 Gateway Timeout específicamente
|
|
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.GatewayTimeout)
|
|
// Esperar y reintentar. La espera se duplica en cada reintento.
|
|
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
|
|
onRetry: (outcome, timespan, retryAttempt, context) =>
|
|
{
|
|
// Opcional: Loguear cada reintento. Necesitarías pasar ILogger aquí.
|
|
// Log.Warning("Retrying due to {StatusCode}. Waited {seconds}s. Attempt {retryAttempt}/3", outcome.Result.StatusCode, timespan.TotalSeconds, retryAttempt);
|
|
});
|
|
} |