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.
This commit is contained in:
@@ -14,6 +14,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.1" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.*" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.*" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.*" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="6.*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -4,82 +4,117 @@ using JdeScoping.DataSync.Options;
|
|||||||
using JdeScoping.ExcelIO.Options;
|
using JdeScoping.ExcelIO.Options;
|
||||||
using JdeScoping.Database;
|
using JdeScoping.Database;
|
||||||
using JdeScoping.Host.Startup;
|
using JdeScoping.Host.Startup;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Options;
|
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)
|
Log.Logger = new LoggerConfiguration()
|
||||||
builder.Host.UseWindowsService();
|
.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)
|
try
|
||||||
// Note: IDatabaseMigrator interface enables mocking for integration tests
|
|
||||||
if (!builder.Environment.IsEnvironment("Testing"))
|
|
||||||
{
|
{
|
||||||
// Create early logger for startup diagnostics
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
using var loggerFactory = LoggerFactory.Create(b => b
|
|
||||||
.AddConfiguration(builder.Configuration.GetSection("Logging"))
|
|
||||||
.AddConsole());
|
|
||||||
var startupLogger = loggerFactory.CreateLogger("JdeScoping.Host.Startup");
|
|
||||||
|
|
||||||
IDatabaseMigrator migrator = new DatabaseMigrator(builder.Configuration);
|
// Windows Service support (no-op on non-Windows)
|
||||||
var migrationResult = migrator.Migrate();
|
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);
|
logging.ClearProviders();
|
||||||
return 1;
|
logging.AddSerilog(dispose: true);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// ASP.NET Core services
|
// Run database migrations (skip in Testing environment)
|
||||||
builder.Services.AddRazorPages();
|
// Note: IDatabaseMigrator interface enables mocking for integration tests
|
||||||
|
if (!builder.Environment.IsEnvironment("Testing"))
|
||||||
// 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<ILoggerFactory>()
|
|
||||||
.CreateLogger("JdeScoping.Host.ConfigurationValidation");
|
|
||||||
|
|
||||||
if (!ConfigurationValidationRunner.ValidateConfiguration(app.Services, configLogger))
|
|
||||||
{
|
{
|
||||||
configLogger.LogCritical("Application startup aborted due to configuration validation failures");
|
IDatabaseMigrator migrator = new DatabaseMigrator(builder.Configuration);
|
||||||
return 1;
|
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<ILoggerFactory>()
|
||||||
|
.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;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
// Startup validation - verify critical services are registered
|
|
||||||
ValidateServices(app.Services);
|
|
||||||
|
|
||||||
// Configure the HTTP request pipeline
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
{
|
||||||
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
|
// Validates that critical services are properly registered
|
||||||
static void ValidateServices(IServiceProvider services)
|
static void ValidateServices(IServiceProvider services)
|
||||||
|
|||||||
Reference in New Issue
Block a user