using System.Globalization; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization.Routing; using Microsoft.Extensions.Options; using Convert_It_Online.Services; using Convert_It_Online.Middleware; using Serilog; using Serilog.Events; using Serilog.Sinks.OpenSearch; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats; using HeyRed.ImageSharp.Heif.Formats.Heif; using HeyRed.ImageSharp.Heif.Formats.Avif; // Configurar suporte HEIF/AVIF para SixLabors.ImageSharp Configuration.Default.Configure(new AvifConfigurationModule()); Configuration.Default.Configure(new HeifConfigurationModule()); var isDevelopment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development"; var hostname = Environment.MachineName; Serilog.Debugging.SelfLog.Enable(msg => { Console.WriteLine($"[SERILOG SELF] {DateTime.Now:HH:mm:ss} {msg}"); System.Diagnostics.Debug.WriteLine($"[SERILOG SELF] {msg}"); }); var builder = WebApplication.CreateBuilder(args); var loggerConfig = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) .Enrich.FromLogContext() .Enrich.WithEnvironmentName() .Enrich.WithProcessId() .Enrich.WithThreadId() .Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "Convert-It") .Enrich.WithProperty("Environment", builder.Environment.EnvironmentName) .Enrich.WithProperty("Hostname", hostname); if (isDevelopment) { loggerConfig .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Information) .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Information) .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("System", LogEventLevel.Warning) .WriteTo.Console( restrictedToMinimumLevel: LogEventLevel.Debug, outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") .WriteTo.File( "./logs/convert-it-dev-.log", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 2, restrictedToMinimumLevel: LogEventLevel.Debug, outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}"); var openSearchUrl = builder.Configuration["Serilog:OpenSearchUrl"]; if (!string.IsNullOrEmpty(openSearchUrl)) { var indexFormat = "convert-it-dev-{0:yyyy-MM}"; try { loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl)) { IndexFormat = indexFormat, AutoRegisterTemplate = true, BufferBaseFilename = "./logs/opensearch-buffer", ModifyConnectionSettings = conn => conn .RequestTimeout(TimeSpan.FromSeconds(8)) .PingTimeout(TimeSpan.FromSeconds(4)), MinimumLogEventLevel = LogEventLevel.Debug, EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog, RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway, BatchPostingLimit = 10, Period = TimeSpan.FromSeconds(2), BufferRetainedInvalidPayloadsLimitBytes = 100 * 1024 * 1024, BufferLogShippingInterval = TimeSpan.FromSeconds(1), TemplateCustomSettings = new Dictionary { {"number_of_shards", "1"}, {"number_of_replicas", "0"} } }), bufferSize: 10000, blockWhenFull: false); } catch (Exception) { // Falha silenciosa - logs continuam no console e arquivo } } } else { loggerConfig .MinimumLevel.Information() .MinimumLevel.Override("Microsoft.AspNetCore.Hosting.Diagnostics", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Routing.EndpointMiddleware", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.StaticFiles", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("System", LogEventLevel.Warning) .WriteTo.Console( restrictedToMinimumLevel: LogEventLevel.Information, outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") .WriteTo.File( "/app/logs/convert-it-.log", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 7, restrictedToMinimumLevel: LogEventLevel.Warning, outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{Hostname}] {Message:lj} {Properties:j}{NewLine}{Exception}"); var openSearchUrl = builder.Configuration["Serilog:OpenSearchUrl"]; if (!string.IsNullOrEmpty(openSearchUrl)) { var environment = builder.Environment.EnvironmentName.ToLower(); var envMapping = environment switch { "production" => "prod", "staging" => "release", "development" => "dev", _ => environment }; var indexFormat = $"convert-it-{envMapping}-{{0:yyyy-MM}}"; try { loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl)) { IndexFormat = indexFormat, AutoRegisterTemplate = false, BufferBaseFilename = "./logs/buffer", ModifyConnectionSettings = conn => conn .RequestTimeout(TimeSpan.FromSeconds(30)) .PingTimeout(TimeSpan.FromSeconds(10)), MinimumLogEventLevel = LogEventLevel.Information, EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog, RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway, BatchPostingLimit = 50, Period = TimeSpan.FromSeconds(5), }), bufferSize: 10000, blockWhenFull: false); } catch (Exception) { // Falha silenciosa em produção - logs continuam no console/arquivo } } } var logger = loggerConfig.CreateLogger(); Log.Logger = logger; Console.WriteLine($"[STARTUP] {DateTime.Now:HH:mm:ss} - Logger configurado"); Log.Information("=== APLICAÇÃO INICIANDO ==="); Log.Information("Convert-It iniciando em {Environment} no host {Hostname}", builder.Environment.EnvironmentName, hostname); builder.Host.UseSerilog(); builder.Services.AddLocalization(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); var supportedCultures = new[] { "pt-BR", "es-MX", "es-CL", "es-PY" }; builder.Services.Configure(options => { options.DefaultRequestCulture = new RequestCulture("pt-BR"); options.SupportedCultures = supportedCultures.Select(c => new CultureInfo(c)).ToList(); options.SupportedUICultures = supportedCultures.Select(c => new CultureInfo(c)).ToList(); }); builder.Services.AddControllersWithViews() .AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix); var app = builder.Build(); var localizationOptions = app.Services.GetRequiredService>().Value; localizationOptions.RequestCultureProviders.Clear(); localizationOptions.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider()); localizationOptions.RequestCultureProviders.Insert(1, new AcceptLanguageHeaderRequestCultureProvider()); localizationOptions.RequestCultureProviders.Insert(2, new QueryStringRequestCultureProvider()); // 3. Pipeline de Middlewares (na ordem correta) if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseExceptionHandler("/Home/Error"); app.UseHsts(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseMiddleware(); app.UseRouting(); app.UseRequestLocalization(localizationOptions); app.UseAuthorization(); // Health endpoint mapping app.MapControllerRoute( name: "health", pattern: "health", defaults: new { controller = "Health", action = "HealthCheck" }); app.MapControllerRoute( name: "uptimeKuma", pattern: "uptime-kuma", defaults: new { controller = "Health", action = "UptimeKuma" }); app.MapControllerRoute( name: "areaRoute", pattern: "{culture:length(2,5)}/{area:exists}/{controller=Home}/{action=Index}/{id?}"); app.MapControllerRoute( name: "default", pattern: "{culture:length(2,5)}/{controller=Home}/{action=Index}/{id?}"); app.MapControllerRoute( name: "root", pattern: "{controller=Home}/{action=Index}/{id?}", defaults: new { culture = "pt-BR" }); Log.Information("=== APLICAÇÃO CONFIGURADA - INICIANDO SERVER ==="); try { app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Aplicação falhou ao iniciar"); throw; } finally { Log.CloseAndFlush(); }