如何在Autofac子作用域中重写Serilog日志记录器?
解决Serilog上下文属性不生效的问题
嘿,我看了你的代码,你踩的坑其实是个日志器类型适配的问题——你在子作用域里注册的是Serilog原生的Serilog.ILogger,但你的MessageProcessingService注入的是.NET Core自带的Microsoft.Extensions.Logging.ILogger<MessageProcessingService>,这俩是不同的接口,Autofac不会自动帮你把它们关联起来,所以你的上下文属性根本没被用到!
下面给你一步步解决的方案:
核心问题拆解
默认情况下,.NET Core的ILogger<T>是通过Serilog.Extensions.Logging包桥接到Serilog根日志器Log.Logger的。但你想在子作用域里用自定义上下文的日志器,就得让这个子作用域里的所有ILogger<T>都使用你创建的带上下文的Serilog实例,而不是根日志器。
调整子作用域的注册逻辑
修改AppModule里的作用域代码,把Microsoft的日志工厂也替换成子作用域专属的:
internal class AppModule : Module { protected override void Load(ContainerBuilder builder) { builder.Register( ctx => { var rootScope = ctx.Resolve<ILifetimeScope>(); return new AutofacHostedServiceScopeOwner( () => rootScope.BeginLifetimeScope(b => { // 1. 创建带自定义上下文的Serilog日志器 var scopedSerilogLogger = Log.Logger.ForContext("name", "value"); // 2. 把这个日志器注册到子作用域 b.RegisterInstance(scopedSerilogLogger).As<Serilog.ILogger>(); // 3. 替换子作用域的ILoggerFactory,让它用我们的Serilog实例 b.Register(ctxScope => new SerilogLoggerFactory(scopedSerilogLogger, dispose: false)) .As<ILoggerFactory>() .SingleInstance(); // 4. 确保ILogger<T>从这个新的工厂创建 b.RegisterGeneric(typeof(Logger<>)) .As(typeof(ILogger<>)) .SingleInstance(); }), scope => new MessageProcessingService( scope.Resolve<ILogger<MessageProcessingService>>() ) ); } ) .As<IHostedService>(); } }
为什么这样能行?
- 我们先在子作用域里注册了带上下文的Serilog原生日志器,确保子作用域里所有依赖
Serilog.ILogger的地方都用它。 - 然后替换了
ILoggerFactory为SerilogLoggerFactory,并传入我们的上下文日志器——这样所有通过这个工厂生成的ILogger<T>都会自动使用这个带上下文的Serilog实例。 - 最后显式注册
Logger<T>(.NET Core的默认ILogger<T>实现),确保Autofac不会用根容器里的日志工厂,而是用我们子作用域的。
验证效果
在MessageProcessingService里加一条日志测试:
public class MessageProcessingService : IHostedService { private readonly ILogger<MessageProcessingService> _logger; public MessageProcessingService(ILogger<MessageProcessingService> logger) { _logger = logger; } public async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Starting message processing service"); // 这里的日志应该会包含"name": "value"属性 await Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Stopping message processing service"); await Task.CompletedTask; } }
运行后看控制台日志,你应该能看到带name: value的上下文属性了。
扩展:给多个实例加唯一上下文
如果要给每个MessageProcessingService实例加不同的唯一上下文(比如实例ID),直接在注册循环里传不同的参数就行:
protected override void Load(ContainerBuilder builder) { // 注册3个不同的实例,每个带唯一的上下文 for (int i = 0; i < 3; i++) { var instanceId = $"ServiceInstance-{i}"; builder.Register( ctx => { var rootScope = ctx.Resolve<ILifetimeScope>(); return new AutofacHostedServiceScopeOwner( () => rootScope.BeginLifetimeScope(b => { var scopedSerilogLogger = Log.Logger.ForContext("InstanceId", instanceId); b.RegisterInstance(scopedSerilogLogger).As<Serilog.ILogger>(); // 下面的LoggerFactory和Logger<T>注册和上面一样 b.Register(ctxScope => new SerilogLoggerFactory(scopedSerilogLogger, dispose: false)) .As<ILoggerFactory>() .SingleInstance(); b.RegisterGeneric(typeof(Logger<>)) .As(typeof(ILogger<>)) .SingleInstance(); }), scope => new MessageProcessingService( scope.Resolve<ILogger<MessageProcessingService>>() ) ); } ).As<IHostedService>(); } }
这样每个实例的日志都会带上自己的InstanceId属性,完美区分不同实例的日志。
内容的提问来源于stack exchange,提问作者Timothy Schoonover
相关产品推荐
相关产品推荐

