diff --git a/.gitea/workflows/deploy-bcards.yml b/.gitea/workflows/deploy-bcards.yml index 6edc650..32b2c18 100644 --- a/.gitea/workflows/deploy-bcards.yml +++ b/.gitea/workflows/deploy-bcards.yml @@ -231,6 +231,9 @@ jobs: "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ] + }, + "Serilog": { + "OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://localhost:9201' }}", } } CONFIG_EOF @@ -291,7 +294,7 @@ jobs: -e MongoDb__ConnectionString="mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/BCardsDB?replicaSet=rs0&authSource=admin" \ -e MongoDb__DatabaseName="BCardsDB" \ -e Logging__LogLevel__Default=Debug \ - -e Serilog__SeqUrl="http://localhost:5343" \ + -e Serilog__OpenSearchUrl="https://localhost:9201" \ -v /home/ubuntu/bcards-config/appsettings.Production.json:/app/appsettings.Production.json:ro \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest @@ -341,7 +344,7 @@ jobs: -e MongoDb__ConnectionString="mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/BCardsDB?replicaSet=rs0&authSource=admin" \ -e MongoDb__DatabaseName="BCardsDB" \ -e Logging__LogLevel__Default=Debug \ - -e Serilog__SeqUrl="http://localhost:5342" \ + -e Serilog__OpenSearchUrl="https://localhost:9202" \ -v /home/ubuntu/bcards-config/appsettings.Production.json:/app/appsettings.Production.json:ro \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest @@ -423,7 +426,7 @@ jobs: --network host \ -e ASPNETCORE_ENVIRONMENT=Staging \ -e ASPNETCORE_URLS=http://+:8080 \ - -e Serilog__SeqUrl="http://localhost:5341" \ + -e Serilog__OpenSearchUrl="https://192.168.0.100:9200" \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }} echo "✅ Servidor Staging atualizado" diff --git a/src/BCards.Web/BCards.Web.csproj b/src/BCards.Web/BCards.Web.csproj index 1819726..921bf20 100644 --- a/src/BCards.Web/BCards.Web.csproj +++ b/src/BCards.Web/BCards.Web.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/BCards.Web/Controllers/StripeWebhookController.cs b/src/BCards.Web/Controllers/StripeWebhookController.cs index 72f7736..ba76b01 100644 --- a/src/BCards.Web/Controllers/StripeWebhookController.cs +++ b/src/BCards.Web/Controllers/StripeWebhookController.cs @@ -91,18 +91,15 @@ public class StripeWebhookController : ControllerBase break; } - await Task.Delay(2000); // 2 segundos return Ok(); } catch (StripeException ex) { - await Task.Delay(2000); // 2 segundos _logger.LogError(ex, "Stripe webhook error"); return BadRequest($"Stripe error: {ex.Message}"); } catch (Exception ex) { - await Task.Delay(2000); // 2 segundos _logger.LogError(ex, "Webhook processing error"); return StatusCode(500, "Internal server error"); } @@ -140,10 +137,12 @@ public class StripeWebhookController : ControllerBase { _logger.LogWarning($"Unexpected event type on HandlePaymentSucceeded: {stripeEvent.Type}"); } + await Task.Delay(4000); } catch (Exception ex) { _logger.LogError(ex, "Error handling payment succeeded event"); + await Task.Delay(4000); throw new Exception("Error handling payment succeeded event", ex); } } @@ -327,15 +326,18 @@ public class StripeWebhookController : ControllerBase // Try to find user by Stripe Customer ID and create/update subscription await HandleSubscriptionCreatedForNewSubscription(stripeSubscription); } + } else { _logger.LogWarning($"Unexpected event type on HandleSubscriptionCreated: {stripeEvent.Type}"); } + await Task.Delay(4000); } catch (Exception ex) { _logger.LogError(ex, "Error handling subscription created event"); + await Task.Delay(4000); throw new Exception("Error handling subscription created event", ex); } } diff --git a/src/BCards.Web/Program.cs b/src/BCards.Web/Program.cs index 15ba091..066bc51 100644 --- a/src/BCards.Web/Program.cs +++ b/src/BCards.Web/Program.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.HttpOverrides; using Serilog; using Serilog.Events; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Serilog.Sinks.OpenSearch; var builder = WebApplication.CreateBuilder(args); @@ -25,6 +26,13 @@ var builder = WebApplication.CreateBuilder(args); var isDevelopment = builder.Environment.IsDevelopment(); var hostname = Environment.MachineName; +// 🔥 CORREÇÃO 1: Habilitar SelfLog ANTES de configurar o logger +Serilog.Debugging.SelfLog.Enable(msg => +{ + Console.WriteLine($"[SERILOG SELF] {DateTime.Now:HH:mm:ss} {msg}"); + System.Diagnostics.Debug.WriteLine($"[SERILOG SELF] {msg}"); +}); + var loggerConfig = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) .Enrich.FromLogContext() @@ -35,65 +43,156 @@ var loggerConfig = new LoggerConfiguration() .Enrich.WithProperty("Environment", builder.Environment.EnvironmentName) .Enrich.WithProperty("Hostname", hostname); -// Nova abordagem de logging: Tudo em ambos, mas com controle de volume if (isDevelopment) { - // Development: Debug level + detalhes completos 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) - // Console: Tudo acima de Information - .WriteTo.Async(a => a.Console( + .WriteTo.Console( restrictedToMinimumLevel: LogEventLevel.Information, - outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [{Hostname}] {Message:lj} {Properties:j}{NewLine}{Exception}")); - - var seqUrl = builder.Configuration["Serilog:SeqUrl"]; - if (!string.IsNullOrEmpty(seqUrl)) + outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [{Hostname}] {Message:lj} {Properties:j}{NewLine}{Exception}"); + + var openSearchUrl = builder.Configuration["Serilog:OpenSearchUrl"]; + if (!string.IsNullOrEmpty(openSearchUrl)) { - var apiKey = builder.Configuration["Serilog:ApiKey"]; - // Seq: Tudo (incluindo Debug) - loggerConfig.WriteTo.Async(a => a.Seq(seqUrl, - apiKey: string.IsNullOrEmpty(apiKey) ? null : apiKey, - restrictedToMinimumLevel: LogEventLevel.Debug)); + var indexFormat = "b-cards-dev-{0:yyyy-MM}"; + + Console.WriteLine($"[DEBUG] Configurando OpenSearch HTTP (SEM AUTENTICAÇÃO):"); + Console.WriteLine($"[DEBUG] URL: {openSearchUrl}"); + Console.WriteLine($"[DEBUG] Index Format: {indexFormat}"); + Console.WriteLine($"[DEBUG] Protocol: {(openSearchUrl.StartsWith("https") ? "HTTPS" : "HTTP")}"); + + try + { + loggerConfig.WriteTo.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl)) + { + IndexFormat = indexFormat, + AutoRegisterTemplate = true, + BufferBaseFilename = "./logs/buffer", + // 🔥 REMOVIDO: BasicAuthentication - SEM AUTENTICAÇÃO + ModifyConnectionSettings = conn => conn + .RequestTimeout(TimeSpan.FromSeconds(30)) + .PingTimeout(TimeSpan.FromSeconds(10)) + .ThrowExceptions(true) + .OnRequestCompleted(details => + { + if (details.Success) + { + Console.WriteLine($"[OPENSEARCH] ✅ Request OK: {details.HttpMethod} {details.Uri} - Status: {details.HttpStatusCode}"); + } + else + { + Console.WriteLine($"[OPENSEARCH] ❌ Request FAILED: {details.HttpMethod} {details.Uri}"); + Console.WriteLine($"[OPENSEARCH] Status: {details.HttpStatusCode}"); + Console.WriteLine($"[OPENSEARCH] Exception: {details.OriginalException?.Message}"); + if (details.ResponseBodyInBytes != null && details.ResponseBodyInBytes.Length > 0) + { + var responseBody = System.Text.Encoding.UTF8.GetString(details.ResponseBodyInBytes); + Console.WriteLine($"[OPENSEARCH] Response: {responseBody}"); + } + } + }), + MinimumLogEventLevel = LogEventLevel.Debug, + EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog, + RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway, + BatchPostingLimit = 5, // Muito pequeno para debug + Period = TimeSpan.FromSeconds(2), + // 🔥 CORRIGIDO: Dictionary + TemplateCustomSettings = new Dictionary + { + {"number_of_shards", "1"}, + {"number_of_replicas", "0"}, + {"index.mapping.total_fields.limit", "2000"} + } + }); + + Console.WriteLine($"[DEBUG] ✅ Serilog configurado para OpenSearch HTTP!"); + } + catch (Exception ex) + { + Console.WriteLine($"[ERROR] ❌ Falha ao configurar OpenSearch: {ex.Message}"); + Console.WriteLine($"[ERROR] Stack trace: {ex.StackTrace}"); + } + } + else + { + Console.WriteLine("[WARNING] OpenSearchUrl não configurada!"); } } else { - // Production: Log balanceado - reduzir spam de requisições + // PRODUÇÃO: Mesma lógica sem autenticação loggerConfig .MinimumLevel.Information() - .MinimumLevel.Override("Microsoft.AspNetCore.Hosting.Diagnostics", LogEventLevel.Warning) // Reduzir spam de requests + .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) - // Docker/Console: Information e acima (reduzindo volume do log) - .WriteTo.Async(a => a.Console( - restrictedToMinimumLevel: LogEventLevel.Information, // Mudança: não só errors - outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] [{Hostname}] {Message:lj}{NewLine}{Exception}")) - // Arquivo de log local para backup (rotativo) - .WriteTo.Async(a => a.File( + .WriteTo.Console( + restrictedToMinimumLevel: LogEventLevel.Information, + outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] [{Hostname}] {Message:lj}{NewLine}{Exception}") + .WriteTo.File( "/app/logs/bcards-.log", rollingInterval: RollingInterval.Day, - retainedFileCountLimit: 7, // Manter 7 dias - restrictedToMinimumLevel: LogEventLevel.Warning, // Só warnings e errors em arquivo - outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] [{Hostname}] {Message:lj} {Properties:j}{NewLine}{Exception}")); - - var seqUrl = builder.Configuration["Serilog:SeqUrl"]; - if (!string.IsNullOrEmpty(seqUrl)) + 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 apiKey = builder.Configuration["Serilog:ApiKey"]; - // Seq: Tudo (Information e acima) - Seq pode estar offline - loggerConfig.WriteTo.Async(a => a.Seq(seqUrl, - apiKey: string.IsNullOrEmpty(apiKey) ? null : apiKey, - restrictedToMinimumLevel: LogEventLevel.Information)); + var environment = builder.Environment.EnvironmentName.ToLower(); + var envMapping = environment switch + { + "production" => "prod", + "staging" => "release", + "development" => "dev", + _ => environment + }; + + var indexFormat = $"b-cards-{envMapping}-{{0:yyyy-MM}}"; + + loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl)) + { + IndexFormat = indexFormat, + AutoRegisterTemplate = false, + BufferBaseFilename = "./logs/buffer", + // 🔥 SEM AUTENTICAÇÃO EM PRODUÇÃO TAMBÉM + 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); } } -Log.Logger = loggerConfig.CreateLogger(); +// 🔥 LOGS DE TESTE MAIS AGRESSIVOS +var logger = loggerConfig.CreateLogger(); +Log.Logger = logger; + +Console.WriteLine($"[STARTUP] {DateTime.Now:HH:mm:ss} - Testando logs SEM AUTENTICAÇÃO..."); +Log.Information("=== TESTE DE LOG SEM AUTH ==="); +Log.Information("Aplicação BCards iniciando em {Environment} no host {Hostname}", + builder.Environment.EnvironmentName, hostname); +Log.Warning("Log de WARNING - teste OpenSearch HTTP"); +Log.Error("Log de ERROR - forçando envio imediato"); +Log.Debug("Log de DEBUG - verificando conectividade"); + +// Force flush para garantir que logs sejam enviados +//Log.CloseAndFlush(); +await Task.Delay(5000); // Aguardar mais tempo + +// Recriar logger após flush +//Log.Logger = loggerConfig.CreateLogger(); +Console.WriteLine($"[STARTUP] {DateTime.Now:HH:mm:ss} - Teste de logs concluído"); // Use Serilog for the host builder.Host.UseSerilog(); @@ -108,7 +207,6 @@ builder.Services.Configure(options => options.RequireHeaderSymmetry = false; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); - // 🚨 PERMITIR QUALQUER PROXY (NGINX) options.ForwardLimit = null; }); @@ -142,19 +240,17 @@ builder.Services.AddControllersWithViews() builder.Services.Configure( builder.Configuration.GetSection("MongoDb")); -// 🔥 OTIMIZAÇÃO: Pool de conexões do MongoDB builder.Services.AddSingleton(serviceProvider => { var settings = serviceProvider.GetRequiredService>().Value; var connectionString = settings.ConnectionString; - // Adiciona configurações de pool de conexões se não existirem if (!connectionString.Contains("maxPoolSize")) { var separator = connectionString.Contains("?") ? "&" : "?"; connectionString += $"{separator}maxPoolSize=200&minPoolSize=20&maxIdleTimeMS=300000&socketTimeoutMS=60000&connectTimeoutMS=30000"; } - + Log.Information("Connecting to MongoDB with optimized pool settings."); return new MongoClient(connectionString); }); @@ -170,7 +266,6 @@ builder.Services.AddScoped(serviceProvider => builder.Services.Configure( builder.Configuration.GetSection("Stripe")); -// Validate Stripe configuration var stripeSettings = builder.Configuration.GetSection("Stripe").Get(); if (stripeSettings == null || string.IsNullOrEmpty(stripeSettings.SecretKey)) { @@ -178,7 +273,7 @@ if (stripeSettings == null || string.IsNullOrEmpty(stripeSettings.SecretKey)) throw new InvalidOperationException("Stripe configuration is required"); } -Log.Information("🔧 Stripe Environment: {Environment} | Test Mode: {IsTestMode}", +Log.Information("🔧 Stripe Environment: {Environment} | Test Mode: {IsTestMode}", stripeSettings.Environment.ToUpper(), stripeSettings.IsTestMode); if (stripeSettings.IsTestMode) @@ -197,7 +292,6 @@ builder.Services.Configure( builder.Services.Configure( builder.Configuration.GetSection("Authentication:Microsoft")); -// Adicionar configurações builder.Services.Configure( builder.Configuration.GetSection("Moderation")); @@ -213,26 +307,21 @@ builder.Services.AddAuthentication(options => options.LogoutPath = "/Auth/Logout"; options.ExpireTimeSpan = TimeSpan.FromHours(8); options.SlidingExpiration = true; - - // 🔥 CORREÇÃO: Configurações de cookie seguras para evitar problemas de correlação options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; - options.Cookie.SameSite = SameSiteMode.Lax; // Essencial para fluxos OAuth - options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // Garante HTTPS + options.Cookie.SameSite = SameSiteMode.Lax; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }) .AddGoogle(options => { var googleAuth = builder.Configuration.GetSection("Authentication:Google"); options.ClientId = googleAuth["ClientId"] ?? ""; options.ClientSecret = googleAuth["ClientSecret"] ?? ""; - options.CallbackPath = "/signin-google"; // Path padrão do Google OAuth + options.CallbackPath = "/signin-google"; options.BackchannelTimeout = TimeSpan.FromSeconds(30); - - // Adicionar configurações mais robustas para Edge options.SaveTokens = true; - options.UsePkce = true; // Usar PKCE para maior segurança + options.UsePkce = true; - // 🔥 CORREÇÃO: Handler para falhas remotas + debugging melhorado options.Events = new OAuthEvents { OnRemoteFailure = context => @@ -240,7 +329,7 @@ builder.Services.AddAuthentication(options => var logger = context.HttpContext.RequestServices.GetRequiredService>(); logger.LogError("🔴 Google OAuth falhou: {Failure}", context.Failure?.Message); logger.LogError("🔴 User Agent: {UserAgent}", context.Request.Headers.UserAgent.ToString()); - + context.Response.Redirect("/Auth/Login?error=google_oauth_failed"); context.HandleResponse(); return Task.CompletedTask; @@ -258,21 +347,17 @@ builder.Services.AddAuthentication(options => var msAuth = builder.Configuration.GetSection("Authentication:Microsoft"); options.ClientId = msAuth["ClientId"] ?? ""; options.ClientSecret = msAuth["ClientSecret"] ?? ""; - options.CallbackPath = "/signin-microsoft"; // Path explícito + options.CallbackPath = "/signin-microsoft"; options.BackchannelTimeout = TimeSpan.FromSeconds(30); - - // Força seleção de conta a cada login options.AuthorizationEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"; options.TokenEndpoint = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; - // 🔥 CORREÇÃO: Handler para falhas remotas e preservação do evento existente options.Events = new OAuthEvents { OnRedirectToAuthorizationEndpoint = context => { var logger = context.HttpContext.RequestServices.GetRequiredService>(); - // Debug info logger.LogWarning($"=== MICROSOFT AUTH DEBUG ==="); logger.LogWarning($"Original RedirectUri: {context.RedirectUri}"); logger.LogWarning($"Request Scheme: {context.Request.Scheme}"); @@ -280,10 +365,8 @@ builder.Services.AddAuthentication(options => logger.LogWarning($"Request Host: {context.Request.Host}"); logger.LogWarning($"X-Forwarded-Proto: {context.Request.Headers["X-Forwarded-Proto"]}"); - // 🚨 FORÇA HTTPS SEMPRE (exceto localhost) var originalUri = context.RedirectUri; - // Se contém bcards.site, força HTTPS if (originalUri.Contains("bcards.site")) { context.RedirectUri = originalUri @@ -293,7 +376,6 @@ builder.Services.AddAuthentication(options => logger.LogWarning($"FORCED HTTPS - Modified RedirectUri: {context.RedirectUri}"); } - // Adiciona prompt=login para forçar seleção de conta var redirectUri = context.RedirectUri; if (!redirectUri.Contains("prompt=")) { @@ -301,7 +383,6 @@ builder.Services.AddAuthentication(options => } logger.LogWarning($"Final RedirectUri: {redirectUri}"); - context.Response.Redirect(redirectUri); return Task.CompletedTask; }, @@ -339,7 +420,6 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); -//builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -351,47 +431,35 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -// Image Storage Service builder.Services.AddScoped(); -// Configure upload limits for file uploads builder.Services.Configure(options => { - options.MultipartBodyLengthLimit = 10 * 1024 * 1024; // 10MB for forms with files + options.MultipartBodyLengthLimit = 10 * 1024 * 1024; options.ValueLengthLimit = int.MaxValue; options.ValueCountLimit = int.MaxValue; options.KeyLengthLimit = int.MaxValue; }); -// 🔥 NOVO: LivePage Services builder.Services.AddScoped(); builder.Services.AddScoped(); -// Add HttpClient for OpenGraphService builder.Services.AddHttpClient(); -// Add SendGrid builder.Services.AddSingleton(provider => { var apiKey = builder.Configuration["SendGrid:ApiKey"]; return new SendGridClient(apiKey); }); -// Background Services builder.Services.AddHostedService(); -// Response Caching builder.Services.AddResponseCaching(); builder.Services.AddMemoryCache(); - builder.Services.AddRazorPages(); -// ===== CONFIGURAÇÃO DOS HEALTH CHECKS POR GRAVIDADE ===== - -// OAuth Health Service (para verificações rápidas de login) builder.Services.AddScoped(); -// HttpClient otimizado para OAuth checks - usar implementação concreta builder.Services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(5); @@ -399,37 +467,28 @@ builder.Services.AddHttpClient(client => }); builder.Services.AddHealthChecks() - // 🔴 CRÍTICO (ALERTA VERMELHO) - Serviços essenciais .AddCheck( name: "critical_services", - failureStatus: HealthStatus.Unhealthy, // Falha = site fora do ar + failureStatus: HealthStatus.Unhealthy, timeout: TimeSpan.FromSeconds(30)) - - // 🟡 DEGRADADO (ALERTA AMARELO) - OAuth providers .AddCheck( - name: "oauth_providers", - failureStatus: HealthStatus.Degraded, // Falha = login offline, site funciona + name: "oauth_providers", + failureStatus: HealthStatus.Degraded, timeout: TimeSpan.FromSeconds(10)) - - // 🟡 DEGRADADO (ALERTA AMARELO) - Serviços auxiliares .AddCheck( name: "sendgrid", - failureStatus: HealthStatus.Degraded, // Email não impede funcionamento + failureStatus: HealthStatus.Degraded, timeout: TimeSpan.FromSeconds(10)) - - // 🟡 DEGRADADO (ALERTA AMARELO) - Recursos do sistema .AddCheck( name: "resources", - failureStatus: HealthStatus.Degraded, // Performance reduzida + failureStatus: HealthStatus.Degraded, timeout: TimeSpan.FromSeconds(5)); -// Registrar health checks customizados no DI builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); -// HttpClient para Critical Services Health Check builder.Services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(15); @@ -438,18 +497,14 @@ builder.Services.AddHttpClient(client => var app = builder.Build(); -// 🔥 PRIMEIRA COISA APÓS BUILD - FORWARDED HEADERS + BASE URL OVERRIDE app.UseForwardedHeaders(); -// 🚨 FORÇA BASE URL HTTPS EM PRODUÇÃO if (!app.Environment.IsDevelopment()) { app.Use(async (context, next) => { - // Força o contexto a pensar que está em HTTPS context.Request.Scheme = "https"; - // Override do Host se necessário if (context.Request.Host.Host == "bcards.site") { context.Request.Host = new HostString("bcards.site", 443); @@ -459,16 +514,14 @@ if (!app.Environment.IsDevelopment()) }); } -// 🔥 OTIMIZAÇÃO: Adiciona headers de segurança app.Use(async (context, next) => { context.Response.Headers.Append("X-Content-Type-Options", "nosniff"); context.Response.Headers.Append("X-Frame-Options", "DENY"); context.Response.Headers.Append("Referrer-Policy", "no-referrer"); context.Response.Headers.Append("Permissions-Policy", "camera=(), microphone=(), geolocation=()"); - - // Remove headers que expõem a tecnologia - context.Response.OnStarting(() => + + context.Response.OnStarting(() => { context.Response.Headers.Remove("Server"); context.Response.Headers.Remove("X-Powered-By"); @@ -478,7 +531,6 @@ app.Use(async (context, next) => await next(); }); -// Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); @@ -486,8 +538,6 @@ if (!app.Environment.IsDevelopment()) } app.UseHttpsRedirection(); - -// 🔥 OTIMIZAÇÃO: Ativar compressão de response app.UseResponseCompression(); app.UseStaticFiles(new StaticFileOptions @@ -496,68 +546,57 @@ app.UseStaticFiles(new StaticFileOptions { var fileName = ctx.File.Name; var extension = Path.GetExtension(fileName).ToLowerInvariant(); - + TimeSpan maxAge; - - // Cache mais agressivo para assets que mudam raramente + if (extension == ".css" || extension == ".js") { - // CSS/JS: 30 dias (podem mudar com updates) maxAge = TimeSpan.FromDays(30); } - else if (extension == ".woff" || extension == ".woff2" || extension == ".ttf" || + else if (extension == ".woff" || extension == ".woff2" || extension == ".ttf" || extension == ".eot" || extension == ".svg" || extension == ".otf") { - // Fontes: 1 ano (raramente mudam) maxAge = TimeSpan.FromDays(365); } - else if (extension == ".png" || extension == ".jpg" || extension == ".jpeg" || + else if (extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".gif" || extension == ".ico" || extension == ".webp") { - // Imagens: 6 meses maxAge = TimeSpan.FromDays(180); } else { - // Outros arquivos: 1 dia maxAge = TimeSpan.FromDays(1); } - + ctx.Context.Response.Headers.CacheControl = $"public,max-age={maxAge.TotalSeconds}"; ctx.Context.Response.Headers.Append("Vary", "Accept-Encoding"); } }); app.UseRouting(); - app.UseRequestLocalization(); - app.UseAuthentication(); app.UseAuthorization(); -// Add custom middleware app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(); -// 🔥 DEBUG MIDDLEWARE MELHORADO - Apenas para desenvolvimento if (app.Environment.IsDevelopment()) { app.Use(async (context, next) => { var logger = context.RequestServices.GetRequiredService>(); - - // Debug geral apenas em desenvolvimento + logger.LogDebug("Request - Path: {Path}, Query: {Query}, Method: {Method}, Scheme: {Scheme}, IsHttps: {IsHttps}, Host: {Host}", - context.Request.Path, context.Request.QueryString, context.Request.Method, + context.Request.Path, context.Request.QueryString, context.Request.Method, context.Request.Scheme, context.Request.IsHttps, context.Request.Host); - // Debug específico para Microsoft signin if (context.Request.Path.StartsWithSegments("/signin-microsoft")) { logger.LogWarning("SIGNIN-MICROSOFT CALLBACK - Path: {Path}, Query: {Query}, Scheme: {Scheme}, IsHttps: {IsHttps}, Host: {Host}, X-Forwarded-Proto: {ForwardedProto}", - context.Request.Path, context.Request.QueryString, context.Request.Scheme, + context.Request.Path, context.Request.QueryString, context.Request.Scheme, context.Request.IsHttps, context.Request.Host, context.Request.Headers["X-Forwarded-Proto"]); } @@ -567,7 +606,6 @@ if (app.Environment.IsDevelopment()) app.UseResponseCaching(); -// Rotas específicas primeiro app.MapControllerRoute( name: "userpage-preview-path", pattern: "page/preview/{category}/{slug}", @@ -584,25 +622,16 @@ app.MapControllerRoute( pattern: "moderation/{action=Dashboard}/{id?}", defaults: new { controller = "Moderation" }); -// Rota principal que vai pegar ?preview=token -//app.MapControllerRoute( -// name: "userpage", -// pattern: "page/{category}/{slug}", -// defaults: new { controller = "UserPage", action = "Display" }, -// constraints: new { category = @"^[a-zA-Z-]+$", slug = @"^[a-z0-9-]+$" }); - -// 🔥 NOVA ROTA: LivePageController para páginas otimizadas de SEO app.MapControllerRoute( name: "livepage", pattern: "page/{category}/{slug}", defaults: new { controller = "LivePage", action = "Display" }, constraints: new { - category = @"^[a-zA-Z0-9\-\u00C0-\u017F]+$", // ← Aceita acentos + category = @"^[a-zA-Z0-9\-\u00C0-\u017F]+$", slug = @"^[a-z0-9-]+$" }); -// Rotas de páginas legais app.MapControllerRoute( name: "privacy-pt", pattern: "privacidade", @@ -628,7 +657,6 @@ app.MapControllerRoute( pattern: "terminos", defaults: new { controller = "Legal", action = "TermsES" }); -// Rota padrão por último app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); @@ -641,30 +669,40 @@ using (var scope = app.Services.CreateScope()) try { - // Initialize themes var existingThemes = await themeService.GetAvailableThemesAsync(); if (!existingThemes.Any()) { await themeService.InitializeDefaultThemesAsync(); } - // Initialize categories var existingCategories = await categoryService.GetAllCategoriesAsync(); if (!existingCategories.Any()) { await categoryService.InitializeDefaultCategoriesAsync(); } + + // 🔥 CORREÇÃO 12: Logs adicionais após inicialização + Log.Information("Default themes and categories initialized successfully"); } catch (Exception ex) { - var logger = scope.ServiceProvider.GetRequiredService>(); - logger.LogError(ex, "Error initializing default data"); + //var logger = scope.ServiceProvider.GetRequiredService>(); + Log.Error(ex, "Error initializing default data"); } } try { Log.Information("BCards application started successfully on {Hostname}", hostname); + + // 🔥 CORREÇÃO 13: Aguardar um pouco para logs serem enviados antes da aplicação rodar + if (isDevelopment) + { + Console.WriteLine("[DEBUG] Aguardando envio de logs iniciais..."); + await Task.Delay(2000); + Console.WriteLine("[DEBUG] Iniciando aplicação..."); + } + app.Run(); } catch (Exception ex) @@ -675,6 +713,9 @@ catch (Exception ex) finally { Log.Information("BCards application shutting down on {Hostname}", hostname); + + // 🔥 CORREÇÃO 14: Aguardar logs finais serem enviados + await Task.Delay(5000); Log.CloseAndFlush(); } diff --git a/src/BCards.Web/appsettings.Development.json b/src/BCards.Web/appsettings.Development.json index badc92c..ccf425a 100644 --- a/src/BCards.Web/appsettings.Development.json +++ b/src/BCards.Web/appsettings.Development.json @@ -14,8 +14,7 @@ "Environment": "test" }, "Serilog": { - "SeqUrl": "http://192.168.0.100:5341", - "ApiKey": "" + "OpenSearchUrl": "http://192.168.0.100:9200", }, "DetailedErrors": true, "MongoDb": { diff --git a/src/BCards.Web/appsettings.json b/src/BCards.Web/appsettings.json index 84bcc87..09345c3 100644 --- a/src/BCards.Web/appsettings.json +++ b/src/BCards.Web/appsettings.json @@ -7,8 +7,9 @@ } }, "Serilog": { - "SeqUrl": "http://172.17.0.1:5341", - "ApiKey": "" + "OpenSearchUrl": "https://localhost:9201", + "OpenSearchUsername": "admin", + "OpenSearchPassword": "C4rn31r0#13" }, "AllowedHosts": "*", "Stripe": {