From 5dd17cbab8679b6452e6efa406c40c1f3995d604 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 28 Jan 2026 16:38:52 -0500 Subject: [PATCH] feat(host): add Serilog logging with console and file sinks Configure structured logging following logging_style.md guidelines with daily rolling log files (30-day retention) and proper shutdown handling. --- .../JdeScoping.Host/JdeScoping.Host.csproj | 4 + NEW/src/JdeScoping.Host/Program.cs | 159 +++++++++++------- 2 files changed, 101 insertions(+), 62 deletions(-) diff --git a/NEW/src/JdeScoping.Host/JdeScoping.Host.csproj b/NEW/src/JdeScoping.Host/JdeScoping.Host.csproj index 4653504..23b0dcb 100644 --- a/NEW/src/JdeScoping.Host/JdeScoping.Host.csproj +++ b/NEW/src/JdeScoping.Host/JdeScoping.Host.csproj @@ -14,6 +14,10 @@ + + + + diff --git a/NEW/src/JdeScoping.Host/Program.cs b/NEW/src/JdeScoping.Host/Program.cs index c9f6f3c..4bfd54e 100644 --- a/NEW/src/JdeScoping.Host/Program.cs +++ b/NEW/src/JdeScoping.Host/Program.cs @@ -4,82 +4,117 @@ using JdeScoping.DataSync.Options; using JdeScoping.ExcelIO.Options; using JdeScoping.Database; using JdeScoping.Host.Startup; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Serilog; +using Serilog.Events; -var builder = WebApplication.CreateBuilder(args); +// Configure Serilog with console and daily rolling file +var logsPath = Path.Combine(AppContext.BaseDirectory, "logs"); +Directory.CreateDirectory(logsPath); -// Windows Service support (no-op on non-Windows) -builder.Host.UseWindowsService(); +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Console( + outputTemplate: "{Timestamp:HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}") + .WriteTo.File( + path: Path.Combine(logsPath, "scoping-.log"), + rollingInterval: RollingInterval.Day, + retainedFileCountLimit: 30, + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") + .CreateLogger(); -// Run database migrations (skip in Testing environment) -// Note: IDatabaseMigrator interface enables mocking for integration tests -if (!builder.Environment.IsEnvironment("Testing")) +try { - // Create early logger for startup diagnostics - using var loggerFactory = LoggerFactory.Create(b => b - .AddConfiguration(builder.Configuration.GetSection("Logging")) - .AddConsole()); - var startupLogger = loggerFactory.CreateLogger("JdeScoping.Host.Startup"); + var builder = WebApplication.CreateBuilder(args); - IDatabaseMigrator migrator = new DatabaseMigrator(builder.Configuration); - var migrationResult = migrator.Migrate(); + // Windows Service support (no-op on non-Windows) + builder.Host.UseWindowsService(); - if (!migrationResult.Successful) + // Replace default logging with Serilog + builder.Services.AddLogging(logging => { - startupLogger.LogError(migrationResult.Error, "Database migration failed: {ErrorMessage}", migrationResult.Error?.Message); - return 1; - } -} + logging.ClearProviders(); + logging.AddSerilog(dispose: true); + }); -// ASP.NET Core services -builder.Services.AddRazorPages(); - -// Module registration (in dependency order) -builder.Services - .AddDataAccess(builder.Configuration) // 1. Database access + search processing - .AddInfrastructure(builder.Configuration) // 2. Infrastructure (JDE/CMS/Auth) - .AddDataSyncServices(builder.Configuration) // 3. Data sync background service - .AddExcelIO(builder.Configuration) // 4. Result export - .AddWebApi(builder.Configuration); // 5. Web API (controllers, auth, SignalR) - -var app = builder.Build(); - -// Configuration validation (skip in Testing environment) -if (!app.Environment.IsEnvironment("Testing")) -{ - var configLogger = app.Services.GetRequiredService() - .CreateLogger("JdeScoping.Host.ConfigurationValidation"); - - if (!ConfigurationValidationRunner.ValidateConfiguration(app.Services, configLogger)) + // Run database migrations (skip in Testing environment) + // Note: IDatabaseMigrator interface enables mocking for integration tests + if (!builder.Environment.IsEnvironment("Testing")) { - configLogger.LogCritical("Application startup aborted due to configuration validation failures"); - return 1; + IDatabaseMigrator migrator = new DatabaseMigrator(builder.Configuration); + var migrationResult = migrator.Migrate(); + + if (!migrationResult.Successful) + { + Log.Error(migrationResult.Error, "Database migration failed: {ErrorMessage}", migrationResult.Error?.Message); + return 1; + } } + + // ASP.NET Core services + builder.Services.AddRazorPages(); + + // Module registration (in dependency order) + builder.Services + .AddDataAccess(builder.Configuration) // 1. Database access + search processing + .AddInfrastructure(builder.Configuration) // 2. Infrastructure (JDE/CMS/Auth) + .AddDataSyncServices(builder.Configuration) // 3. Data sync background service + .AddExcelIO(builder.Configuration) // 4. Result export + .AddWebApi(builder.Configuration); // 5. Web API (controllers, auth, SignalR) + + var app = builder.Build(); + + // Configuration validation (skip in Testing environment) + if (!app.Environment.IsEnvironment("Testing")) + { + var configLogger = app.Services.GetRequiredService() + .CreateLogger("JdeScoping.Host.ConfigurationValidation"); + + if (!ConfigurationValidationRunner.ValidateConfiguration(app.Services, configLogger)) + { + Log.Fatal("Application startup aborted due to configuration validation failures"); + return 1; + } + } + + // Startup validation - verify critical services are registered + ValidateServices(app.Services); + + // Configure the HTTP request pipeline + if (app.Environment.IsDevelopment()) + { + app.UseWebAssemblyDebugging(); + } + + app.UseStaticFiles(); + app.UseBlazorFrameworkFiles(); + app.UseRouting(); + + // Configure Web API middleware (authentication, authorization, controllers, SignalR hub) + app.UseWebApi(); + + app.MapRazorPages(); + app.MapFallbackToFile("index.html"); + + // Register shutdown handler for clean Serilog disposal + app.Lifetime.ApplicationStopping.Register(Log.CloseAndFlush); + + app.Run(); + + return 0; } - -// Startup validation - verify critical services are registered -ValidateServices(app.Services); - -// Configure the HTTP request pipeline -if (app.Environment.IsDevelopment()) +catch (Exception ex) { - app.UseWebAssemblyDebugging(); + Log.Fatal(ex, "Application terminated unexpectedly"); + return 1; +} +finally +{ + Log.CloseAndFlush(); } - -app.UseStaticFiles(); -app.UseBlazorFrameworkFiles(); -app.UseRouting(); - -// Configure Web API middleware (authentication, authorization, controllers, SignalR hub) -app.UseWebApi(); - -app.MapRazorPages(); -app.MapFallbackToFile("index.html"); - -app.Run(); - -return 0; // Validates that critical services are properly registered static void ValidateServices(IServiceProvider services)