调整Serilog异步接收器,移除CI/CD集成测试中的EventLog
问题
在Linux代理的CI/CD环境中执行ASP.NET Core 7应用的集成测试时,因Serilog.Sinks.EventLog不支持Linux,初始化Serilog配置时触发PlatformNotSupported异常。
应用生产环境需部署为Windows服务并写入EventLog,但测试环境(Linux CI/CD)不需要EventLog接收器,希望尽可能少修改应用源码,仅调整测试项目代码解决问题。
背景信息
- 使用
Microsoft.AspNetCore.Mvc.Testing的WebApplicationFactory编写集成测试 - 生产环境Serilog配置包含Console和EventLog接收器,测试环境仅需Console
- 已尝试:设置
UseEnvironment("Testing")无效;注入appsettings.Testing.json但EventLog配置仍被加载
现有代码片段
测试工厂配置
protected override void ConfigureWebHost(IWebHostBuilder builder) { var connBuilder = new SqlConnectionStringBuilder(_dbContainer.GetConnectionString()) { InitialCatalog = "start-db", }; _ = builder.ConfigureTestServices(services => services .AddDefaultTestAssets() .InjectTestConnectionStringProvider(connBuilder.ConnectionString) .InjectTestingDb(connBuilder.ConnectionString) .AddTestSecurity()) .ConfigureAppConfiguration((c, b) => { _ = b.AddJsonFile("appsettings.Testing.json"); // 尝试的解决方法之一 c.HostingEnvironment.EnvironmentName = "Testing"; }); }
应用Program.cs核心代码
var builder = WebApplication.CreateBuilder(); builder.AddServiceOptions(); builder.Host.UseSerilog(); builder.Configuration.AddEnvironmentVariables("SERVICE__"); // 服务注册... var app = builder.Build(); Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(app.Configuration).CreateLogger(); // 中间件配置... try { await app.RunAsync(); } catch (Exception ex) { Log.Logger.Critical(ex, "Application terminated unexpectedly"); } finally { Log.Logger.Information("[{LogEvent:l}]The service is closing", LogEvent.AppClosing); Log.CloseAndFlush(); }
生产环境Serilog配置(appsettings.json)
"Serilog": { "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.EventLog" ], "MinimumLevel": "Information", "WriteTo": [ { "Name": "Async", "Args": { "configure": [ { "Name": "Console", "Args": { "outputTemplate": "[{Timestamp:u}][{Application}][{MachineName}][{ProcessName}:{ProcessId}][{ThreadId}][{SourceContext}][{Level:u3}]{Message}{NewLine}{Exception}" } }, { "Name": "EventLog", "Args": { "source": "{Application}", "restrictedToMinimumLevel": "Error", "logName": "Application", "manageEventSource": true, "outputTemplate": "[{Timestamp:u}][{Application}][{MachineName}][{ProcessName}:{ProcessId}][{ThreadId}][{SourceContext}][{Level:u3}]{Message}{NewLine}{Exception}" } } ] } } ], "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId", "WithProcessId", "WithProcessName" ], "Properties": { "Application": "SEM.NG.SqlScriptExecutor" } }
解决方案
方法1:通过环境变量覆盖Serilog配置(仅修改测试项目)
在测试工厂中添加内存配置项,直接覆盖Serilog的EventLog相关节点,避免其被加载:
protected override void ConfigureWebHost(IWebHostBuilder builder) { var connBuilder = new SqlConnectionStringBuilder(_dbContainer.GetConnectionString()) { InitialCatalog = "start-db", }; _ = builder.ConfigureTestServices(services => services .AddDefaultTestAssets() .InjectTestConnectionStringProvider(connBuilder.ConnectionString) .InjectTestingDb(connBuilder.ConnectionString) .AddTestSecurity()) .ConfigureAppConfiguration((ctx, configBuilder) => { // 加载测试环境配置文件 configBuilder.AddJsonFile("appsettings.Testing.json", optional: false, reloadOnChange: true); // 覆盖Serilog配置,移除EventLog相关项 configBuilder.AddInMemoryCollection(new Dictionary<string, string> { ["Serilog:Using:1"] = "", ["Serilog:WriteTo:0:Args:configure:1"] = "" }); ctx.HostingEnvironment.EnvironmentName = "Testing"; }); }
确保appsettings.Testing.json中仅包含Console接收器配置(如你提供的示例)。
方法2:平台判断动态过滤接收器(仅修改应用1处代码)
在Program.cs初始化Serilog时,根据操作系统平台自动跳过EventLog配置:
var loggerConfig = new LoggerConfiguration().ReadFrom.Configuration(app.Configuration); // Linux环境下移除EventLog接收器 if (!OperatingSystem.IsWindows()) { loggerConfig = loggerConfig.WriteTo.Remove("EventLog"); } Log.Logger = loggerConfig.CreateLogger();
这个修改极小,不影响生产环境(Windows)的EventLog功能,同时自动适配Linux测试环境。
方法3:利用ASP.NET Core环境分层配置(无代码修改)
将EventLog配置拆分到生产环境专属配置文件中:
- appsettings.json(公共基础配置):
"Serilog": { "Using": [ "Serilog.Sinks.Console" ], "MinimumLevel": "Information", "WriteTo": [ { "Name": "Async", "Args": { "configure": [ { "Name": "Console", "Args": { "outputTemplate": "[{Timestamp:u}][{Application}][{MachineName}][{ProcessName}:{ProcessId}][{ThreadId}][{SourceContext}][{Level:u3}]{Message}{NewLine}{Exception}" } } ] } } ], "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId", "WithProcessId", "WithProcessName" ], "Properties": { "Application": "SEM.NG.SqlScriptExecutor" } }
- appsettings.Production.json(生产环境追加配置):
"Serilog": { "Using": [ "Serilog.Sinks.EventLog" ], "WriteTo": [ { "Name": "Async", "Args": { "configure": [ { "Name": "EventLog", "Args": { "source": "{Application}", "restrictedToMinimumLevel": "Error", "logName": "Application", "manageEventSource": true, "outputTemplate": "[{Timestamp:u}][{Application}][{MachineName}][{ProcessName}:{ProcessId}][{ThreadId}][{SourceContext}][{Level:u3}]{Message}{NewLine}{Exception}" } } ] } } ] }
然后在测试工厂中直接设置测试环境:
protected override void ConfigureWebHost(IWebHostBuilder builder) { var connBuilder = new SqlConnectionStringBuilder(_dbContainer.GetConnectionString()) { InitialCatalog = "start-db", }; _ = builder.ConfigureTestServices(services => services .AddDefaultTestAssets() .InjectTestConnectionStringProvider(connBuilder.ConnectionString) .InjectTestingDb(connBuilder.ConnectionString) .AddTestSecurity()) .UseEnvironment("Testing"); }
ASP.NET Core会自动加载appsettings.Testing.json,不会加载生产环境的EventLog配置。
方法4:测试中直接替换Serilog配置(完全隔离测试环境)
在测试工厂中直接配置测试专属的Serilog实例,完全绕过应用的配置读取:
protected override void ConfigureWebHost(IWebHostBuilder builder) { var connBuilder = new SqlConnectionStringBuilder(_dbContainer.GetConnectionString()) { InitialCatalog = "start-db", }; _ = builder.ConfigureTestServices(services => services .AddDefaultTestAssets() .InjectTestConnectionStringProvider(connBuilder.ConnectionString) .InjectTestingDb(connBuilder.ConnectionString) .AddTestSecurity()) .UseSerilog((ctx, config) => { config .MinimumLevel.Information() .WriteTo.Async(wt => wt.Console(outputTemplate: "[{Timestamp:u}][{Application}][{MachineName}][{ProcessName}:{ProcessId}][{ThreadId}][{SourceContext}][{Level:u3}]{Message}{NewLine}{Exception}")) .Enrich.FromLogContext() .Enrich.WithMachineName() .Enrich.WithThreadId() .Enrich.WithProcessId() .Enrich.WithProcessName() .Enrich.WithProperty("Application", "SEM.NG.SqlScriptExecutor"); }); }
此方法完全隔离测试环境的日志配置,无需依赖任何appsettings文件。
内容的提问来源于stack exchange,提问作者KUTlime
相关产品推荐
相关产品推荐

