diff --git a/SVSim.EmulatedEntrypoint/Program.cs b/SVSim.EmulatedEntrypoint/Program.cs index e5ec51ea..e3e2355d 100644 --- a/SVSim.EmulatedEntrypoint/Program.cs +++ b/SVSim.EmulatedEntrypoint/Program.cs @@ -20,6 +20,8 @@ using SVSim.EmulatedEntrypoint.Middlewares; using SVSim.EmulatedEntrypoint.Security.SteamSessionAuthentication; using SVSim.EmulatedEntrypoint.Services; using SVSim.BattleNode.Hosting; +using Serilog; +using SVSim.Hosting; namespace SVSim.EmulatedEntrypoint; @@ -29,6 +31,13 @@ public class Program { var builder = WebApplication.CreateBuilder(args); + // Always register Serilog. The file sink self-gates on the Testing environment inside + // the UseSvSimSerilog callback — a caller-side check here would be too early because + // WebApplicationFactory applies UseEnvironment("Testing") after CreateBuilder returns. + builder.Host.UseSvSimSerilog("svsim-api"); + + try + { // Add services to the container. builder.Services.AddControllers().AddJsonOptions(opt => @@ -253,5 +262,15 @@ public class Program app.MapControllers(); app.Run(); + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly"); + throw; + } + finally + { + Log.CloseAndFlush(); + } } } diff --git a/SVSim.EmulatedEntrypoint/SVSim.EmulatedEntrypoint.csproj b/SVSim.EmulatedEntrypoint/SVSim.EmulatedEntrypoint.csproj index 3ba3dd94..65d4f80c 100644 --- a/SVSim.EmulatedEntrypoint/SVSim.EmulatedEntrypoint.csproj +++ b/SVSim.EmulatedEntrypoint/SVSim.EmulatedEntrypoint.csproj @@ -39,6 +39,7 @@ + diff --git a/SVSim.EmulatedEntrypoint/appsettings.json b/SVSim.EmulatedEntrypoint/appsettings.json index 01772859..dee87515 100644 --- a/SVSim.EmulatedEntrypoint/appsettings.json +++ b/SVSim.EmulatedEntrypoint/appsettings.json @@ -9,6 +9,18 @@ "Microsoft.EntityFrameworkCore.Infrastructure": "Warning" } }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft.AspNetCore": "Warning", + "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information", + "Microsoft.EntityFrameworkCore": "Warning", + "Microsoft.EntityFrameworkCore.Database.Command": "Warning", + "Microsoft.EntityFrameworkCore.Infrastructure": "Warning" + } + } + }, "ConnectionStrings": { "ApplicationDb": "Host=localhost;Database=svsim;Username=postgres;password=postgres" }, diff --git a/SVSim.Hosting/SerilogHostingExtensions.cs b/SVSim.Hosting/SerilogHostingExtensions.cs index b6f91eb9..c4413ea7 100644 --- a/SVSim.Hosting/SerilogHostingExtensions.cs +++ b/SVSim.Hosting/SerilogHostingExtensions.cs @@ -1,3 +1,4 @@ +using System.Reflection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Serilog; @@ -8,6 +9,14 @@ namespace SVSim.Hosting; public static class SerilogHostingExtensions { + // Detects `dotnet test` runs regardless of ASPNETCORE_ENVIRONMENT wiring. The env check + // via HostingEnvironment.IsEnvironment("Testing") is unreliable here because + // WebApplicationFactory sets the environment via a mechanism that fires after + // both CreateBuilder and the UseSerilog callback resolve their environment view. The entry + // assembly under NUnit + dotnet test is always "testhost" or "testhost.x86". + private static readonly bool IsTestHost = + Assembly.GetEntryAssembly()?.GetName().Name?.StartsWith("testhost", StringComparison.OrdinalIgnoreCase) == true; + // Wires Serilog as the host's logging provider. Sinks, rotation, retention, // and enrichment defaults are baked in here; per-category level overrides // ride via appsettings under "Serilog:MinimumLevel:Override". The file sink @@ -28,15 +37,23 @@ public static class SerilogHostingExtensions .Enrich.WithThreadId() .WriteTo.Console( theme: AnsiConsoleTheme.Code, - outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}") - .WriteTo.File( + outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}"); + + // Skip the file sink under `dotnet test` so unit-test runs don't scatter log files + // under SVSim.UnitTests/bin/. See IsTestHost above for why we detect via entry + // assembly instead of the ASP.NET environment name. + if (!IsTestHost) + { + loggerConfig.WriteTo.File( path: filePathBase, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true, fileSizeLimitBytes: 100_000_000, retainedFileCountLimit: 7, - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}") - .ReadFrom.Configuration(context.Configuration); + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}"); + } + + loggerConfig.ReadFrom.Configuration(context.Configuration); }); } }