using Managing.Application.Workers; using Managing.Bootstrap; using Managing.Common; using Managing.Infrastructure.Databases.InfluxDb.Models; using Managing.Infrastructure.Databases.PostgreSql; using Managing.Infrastructure.Databases.PostgreSql.Configurations; using Microsoft.EntityFrameworkCore; using Npgsql; // Explicitly set the environment before creating the host builder var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Development"; var hostBuilder = new HostBuilder() .UseEnvironment(environment) .UseContentRoot(AppContext.BaseDirectory); var host = hostBuilder .ConfigureAppConfiguration((hostingContext, config) => { var detectedEnv = hostingContext.HostingEnvironment.EnvironmentName; if (detectedEnv != environment) { Console.WriteLine($"⚠️ WARNING: Environment mismatch! Expected: {environment}, Got: {detectedEnv}"); } config.SetBasePath(AppContext.BaseDirectory); // Load configuration files in order (later files override earlier ones) // 1. Base appsettings.json (always loaded) config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); // 2. Load ONLY the environment-specific file (not other environments) if (!string.IsNullOrEmpty(detectedEnv)) { var envFile = $"appsettings.{detectedEnv}.json"; config.AddJsonFile(envFile, optional: true, reloadOnChange: true); } // 3. Environment variables (highest priority) config.AddEnvironmentVariables(); // User secrets only in development (requires ASP.NET Core, so we skip in production) if (detectedEnv == "Development") { try { config.AddUserSecrets(); } catch { // User secrets not available, skip silently } } }) .ConfigureServices((hostContext, services) => { var configuration = hostContext.Configuration; // Initialize Sentry SentrySdk.Init(options => { options.Dsn = configuration["Sentry:Dsn"]; options.Debug = false; options.SendDefaultPii = true; options.AutoSessionTracking = true; options.IsGlobalModeEnabled = false; options.TracesSampleRate = 0.1; options.Environment = hostContext.HostingEnvironment.EnvironmentName; }); // Configure database var postgreSqlConnectionString = configuration.GetSection(Constants.Databases.PostgreSql)["ConnectionString"]; services.Configure(configuration.GetSection(Constants.Databases.PostgreSql)); services.Configure(configuration.GetSection(Constants.Databases.InfluxDb)); // Build connection string with timeout and pooling settings var connectionStringBuilder = new NpgsqlConnectionStringBuilder(postgreSqlConnectionString) { // Configure connection timeout (default is 15 seconds, increase for network latency) Timeout = 30, // 30 seconds for connection establishment CommandTimeout = 60, // 60 seconds for command execution // Configure connection pooling for better performance and reliability MaxPoolSize = 100, // Maximum pool size MinPoolSize = 5, // Minimum pool size // Configure KeepAlive to maintain connections and detect network issues KeepAlive = 300 // 5 minutes keepalive interval }; var enhancedConnectionString = connectionStringBuilder.ConnectionString; // Add DbContext services.AddDbContext((serviceProvider, options) => { options.UseNpgsql(enhancedConnectionString, npgsqlOptions => { // Enable retry on failure for transient errors npgsqlOptions.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10), errorCodesToAdd: null); }); if (hostContext.HostingEnvironment.IsDevelopment()) { options.EnableDetailedErrors(); options.EnableSensitiveDataLogging(); // SQL logging disabled - uncomment below line if needed for debugging // options.LogTo(Console.WriteLine, LogLevel.Information); } options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); options.EnableServiceProviderCaching(); }, ServiceLifetime.Scoped); // Register compute dependencies (no Orleans) services.RegisterComputeDependencies(configuration); // Configure BacktestComputeWorker options services.Configure( configuration.GetSection(BacktestComputeWorkerOptions.SectionName)); // Get task slot from CapRover ({{.Task.Slot}}) or environment variable // This identifies which instance of the worker is running var taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ?? Environment.GetEnvironmentVariable("CAPROVER_TASK_SLOT") ?? "0"; // Override WorkerId from environment variable if provided, otherwise use task slot var workerId = Environment.GetEnvironmentVariable("WORKER_ID") ?? configuration["BacktestComputeWorker:WorkerId"] ?? $"{Environment.MachineName}-{taskSlot}"; services.Configure(options => { options.WorkerId = workerId; }); // Configure GeneticComputeWorker options services.Configure( configuration.GetSection(GeneticComputeWorkerOptions.SectionName)); // Override Genetic WorkerId from environment variable if provided, otherwise use task slot var geneticWorkerId = Environment.GetEnvironmentVariable("GENETIC_WORKER_ID") ?? configuration["GeneticComputeWorker:WorkerId"] ?? $"{Environment.MachineName}-genetic-{taskSlot}"; services.Configure(options => { options.WorkerId = geneticWorkerId; }); // Register the backtest compute worker if enabled var isBacktestWorkerEnabled = configuration.GetValue("WorkerBacktestCompute", false); if (isBacktestWorkerEnabled) { services.AddHostedService(); } // Register the genetic compute worker if enabled var isGeneticWorkerEnabled = configuration.GetValue("WorkerGeneticCompute", false); if (isGeneticWorkerEnabled) { services.AddHostedService(); } }) .ConfigureLogging((hostingContext, logging) => { logging.ClearProviders(); logging.AddConsole(); logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); // Filter out EF Core database command logs (SQL queries) logging.AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Warning); }) .Build(); var logger = host.Services.GetRequiredService>(); var config = host.Services.GetRequiredService(); // Test database connection at startup var postgreSqlConnectionString = config.GetSection(Constants.Databases.PostgreSql)["ConnectionString"]; if (string.IsNullOrEmpty(postgreSqlConnectionString)) { logger.LogWarning("⚠️ Database connection string is empty or not configured!"); } else { // Parse and log database host name try { var connectionParts = postgreSqlConnectionString.Split(';') .Where(p => !string.IsNullOrWhiteSpace(p)) .Select(p => p.Trim()) .ToDictionary( p => p.Split('=')[0].Trim(), p => p.Contains('=') ? p.Substring(p.IndexOf('=') + 1).Trim() : string.Empty, StringComparer.OrdinalIgnoreCase); var dbHost = connectionParts.GetValueOrDefault("Host", "unknown"); logger.LogWarning("📊 Database Host: {Host}", dbHost); } catch { // Failed to parse connection string, continue anyway } try { using var scope = host.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); var canConnect = await dbContext.Database.CanConnectAsync(); if (!canConnect) { logger.LogWarning("⚠️ Database connection test failed - Cannot connect to database"); } } catch (Exception ex) { logger.LogError(ex, "❌ Database connection test failed - {Error}", ex.Message); } } var isBacktestWorkerEnabled = config.GetValue("WorkerBacktestCompute", false); var isGeneticWorkerEnabled = config.GetValue("WorkerGeneticCompute", false); if (!isBacktestWorkerEnabled) { logger.LogWarning("BacktestComputeWorker is disabled via configuration. No backtest jobs will be processed."); } if (!isGeneticWorkerEnabled) { logger.LogWarning("GeneticComputeWorker is disabled via configuration. No genetic jobs will be processed."); } try { await host.RunAsync(); } catch (Exception ex) { logger.LogCritical(ex, "Application terminated unexpectedly"); SentrySdk.CaptureException(ex); throw; } finally { SentrySdk.FlushAsync(TimeSpan.FromSeconds(2)).Wait(); }