using ECMJobRunner.Application; using ECMJobRunner.Infrastructure; using ECMJobRunner.WebCron; using Hangfire; using Hangfire.SqlServer; using Microsoft.Data.SqlClient; using Serilog; using Serilog.Ui.Core.Extensions; using Serilog.Ui.SqliteDataProvider.Extensions; using Serilog.Ui.Web.Extensions; // Enable Serilog self-diagnostics Serilog.Debugging.SelfLog.Enable(msg => Console.WriteLine($"[SERILOG] {msg}")); // Build temporary configuration to read log directory var tempConfig = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .Build(); // Get log directory from configuration var logDirectory = tempConfig.GetValue("Logging:LogDirectory") ?? throw new InvalidOperationException("Logging:LogDirectory not found in configuration."); var sqliteDbPath = Path.Combine(logDirectory, "logs.db"); Console.WriteLine($"[INFO] SQLite Log Database Path: {sqliteDbPath}"); // Configure Serilog with SQLite sink Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .MinimumLevel.Override("Microsoft.AspNetCore", Serilog.Events.LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.EntityFrameworkCore", Serilog.Events.LogEventLevel.Warning) .MinimumLevel.Override("Hangfire", Serilog.Events.LogEventLevel.Information) .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") .WriteTo.SQLite(sqliteDbPath, storeTimestampInUtc: true) .Enrich.FromLogContext() .CreateLogger(); try { Log.Information("Starting ECMJobRunner.WebCron application"); var builder = WebApplication.CreateBuilder(args); // Use Serilog for logging builder.Host.UseSerilog(); // Configure Windows Service hosting if enabled if (builder.Configuration.GetValue("HostingOptions:UseWindowsService")) { builder.Host.UseWindowsService(); } // Add services to the container. builder.Services.AddControllers(); builder.Services.AddHostedService(); // Register services var cnnStr = builder.Configuration.GetConnectionString("SDD-VMP04-SQL17") ?? throw new InvalidOperationException("Connection string 'SDD-VMP04-SQL17' not found."); builder.Services.AddJobRunnerInfrastructure(cnnStr); var recClientApiUrl = builder.Configuration.GetValue("ReC:ApiUrl") ?? throw new InvalidOperationException("ReC:ApiUrl not found."); builder.Services.AddJobRunnerServices(recClientApiUrl); // Get Hangfire storage configuration var useInMemory = builder.Configuration.GetValue("Hangfire:InMemory"); // Add Hangfire services with configurable storage builder.Services.AddHangfire(configuration => { configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings(); // Configure storage based on appsettings if (useInMemory) { configuration.UseInMemoryStorage(); } else // Use SQL Server storage { configuration.UseSqlServerStorage(cnnStr, new SqlServerStorageOptions { CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), QueuePollInterval = TimeSpan.Zero, UseRecommendedIsolationLevel = true, DisableGlobalLocks = true, SqlClientFactory = SqlClientFactory.Instance }); } }); // Add Hangfire server builder.Services.AddHangfireServer(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // Add Serilog.UI with SQLite provider - use same path from configuration var serilogUiLogDirectory = builder.Configuration.GetValue("Logging:LogDirectory") ?? throw new InvalidOperationException("Logging:LogDirectory not found in configuration."); var serilogUiDbPath = Path.Combine(serilogUiLogDirectory, "logs.db"); builder.Services.AddSerilogUi(logUIOpt => { logUIOpt.UseSqliteServer(dbOpt => { dbOpt.WithConnectionString($"Data Source={serilogUiDbPath}"); dbOpt.WithTable("Logs"); }); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); // Add Hangfire Dashboard with no authentication (for development) app.UseHangfireDashboard("/hangfire", new DashboardOptions { Authorization = new[] { new AllowAllDashboardAuthorizationFilter() } }); // Add Serilog.UI Dashboard app.UseSerilogUi(); app.MapControllers(); app.Run(); } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }