使用Serilog时LoggerMessage模式性能下降?求原因与优化方案
问题
按照微软的建议,我尝试将其高性能日志方案(即LoggerMessage模式)与Serilog日志记录器结合使用,但未获得任何性能提升,反而导致性能下降。以下是基准测试代码:
[MemoryDiagnoser] public class SerilogBenchmark { private static readonly ILogger<SerilogBenchmark> Logger; private static readonly string LogStringTemplate = "User {Username} {Email} has logged in."; private static Action<Microsoft.Extensions.Logging.ILogger, string, string, Exception?> LoggerAction = LoggerMessage.Define<string, string>( LogLevel.Information, new EventId(1, "Login"), LogStringTemplate); static SerilogBenchmark() { var services = new ServiceCollection(); Log.Logger = new LoggerConfiguration() .WriteTo.TextWriter(TextWriter.Null) .CreateLogger(); services.AddLogging(builder => { builder.ClearProviders(); builder.AddSerilog(dispose: true); }); var serviceProvider = services.BuildServiceProvider(); Logger = serviceProvider.GetRequiredService<ILogger<SerilogBenchmark>>(); } [Benchmark] public void TestLogExtension() { Logger.LogInformation(LogStringTemplate, "User", "foo@bar.com"); } [Benchmark] public void TestLogAction() { LoggerAction(Logger, "User", "foo@bar.com", null); } }
测试结果显示,该"高性能日志"方案使内存分配增加50%,性能降低25%。恳请帮忙分析问题原因,并指导如何结合Serilog实现日志性能提升?
分析与解决方案
问题原因
- 适配器层额外开销:Serilog通过
Serilog.Extensions.Logging适配微软ILogger接口时,LoggerMessage生成的委托会触发模板格式的二次转换——微软原生模板需要映射到Serilog的模板逻辑,这中间产生了冗余的计算和内存分配。 - 无效参数传递:
LoggerMessage.Define生成的委托强制要求传递Exception?参数(即使传入null),而直接调用LogInformation扩展方法时,Serilog适配层会跳过无效参数的处理逻辑,减少不必要的开销。 - 测试场景的特殊性:当前测试输出到
TextWriter.Null,Serilog对空输出有专门的短路优化,但LoggerMessage的委托调用路径绕开了这部分优化,反而引入了冗余操作。
优化方案
1. 直接使用Serilog原生API
放弃微软LoggerMessage,使用Serilog原生强类型日志接口,彻底避免适配器开销:
// 跳过微软ILogger适配器,直接使用Serilog日志器 private static readonly ILogger SerilogLogger = Log.ForContext<SerilogBenchmark>(); [Benchmark] public void TestSerilogNative() { SerilogLogger.Information("User {Username} {Email} has logged in.", "User", "foo@bar.com"); }
这种方式完全利用Serilog的原生优化,能显著降低内存分配并提升性能。
2. 优化LoggerMessage的使用(保留微软ILogger)
如果必须依赖微软ILogger接口,可通过以下方式减少开销:
- 移除无效Exception参数:使用.NET 6+支持的
LoggerMessage.DefineWithoutException生成委托,跳过Exception参数传递:
private static Action<ILogger, string, string> LoggerAction = LoggerMessage.DefineWithoutException<string, string>( LogLevel.Information, new EventId(1, "Login"), LogStringTemplate); // 调用时无需传入null LoggerAction(Logger, "User", "foo@bar.com");
- 开启Serilog适配优化:配置Serilog时启用微软日志优化开关,减少模板转换开销:
services.AddLogging(builder => { builder.ClearProviders(); builder.AddSerilog(dispose: true, configure: l => l.EnableMicrosoftLoggingOptimizations()); });
3. 调整基准测试场景
- 改用真实日志输出(如文件、控制台)测试,
TextWriter.Null的特殊优化会导致基准结果失真; - 增加日志级别过滤测试(如设置为Warning),此时
LoggerMessage在日志被过滤时的性能优势会更明显。
内容的提问来源于stack exchange,提问作者the_V
相关产品推荐
相关产品推荐

