如何在 Autofac 子作用域中重写 Serilog 记录器



我有一个基本的.NET Core Generic Host,它使用Serilog和Autofac。

public class Program
{
    public static async Task Main(string[] args) 
    {
        var configuration = CreateConfiguration(args);
        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(configuration)
            .CreateLogger();
        try 
        {
            Log.Information("Starting host...");
            await new HostBuilder()
                .UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .ConfigureHostConfiguration(cb => cb.AddConfiguration(configuration))
                .ConfigureServices((ctx, sc) => {
                    sc.AddLogging(lb => lb.AddSerilog());
                })
                .ConfigureContainer<ContainerBuilder>(cb => { 
                    cb.RegisterModule(new AppModule()); 
                })
                .RunConsoleAsync();
        }
        catch (Exception ex) {
            Log.Fatal(ex, "Host terminated unexpectedly");
        }
        finally {
            Log.CloseAndFlush();
        }
    }
    private static IConfiguration CreateConfiguration(IReadOnlyCollection<string> args) 
    {
        return new ConfigurationBuilder().SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
            .AddJsonFile("appsettings.json", false, true)
            .AddUserSecrets<Program>(true)
            .AddCommandLine(args.ToArray())
            .Build();
    }
}

AppModule是一个 Autofac 模块,我在其中注册了一个IHostedService

internal class AppModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.Register(
                ctx => {
                    var c = ctx.Resolve<IComponentContext>();
                    return new AutofacHostedServiceScopeOwner(
                        () => c.Resolve<ILifetimeScope>()
                            .BeginLifetimeScope(b => b.Register(x => Log.Logger.ForContext("name", "value")).As<Serilog.ILogger>()),
                        scope => new MessageProcessingService(
                            scope.Resolve<ILogger<MessageProcessingService>>()
                        )
                    );
                }
            )
            .As<IHostedService>();
    }
}

这部分看起来很复杂,但我试图完成的是MessageProcessingService 在 Autofac 子作用域中运行(IHostedService实现(,以便我可以使用一些特定于上下文的信息覆盖整个MessageProcessingService图中解析的记录器。将有多个MessageProcessingService实例,每个实例都需要一个具有唯一上下文的记录器,以便我可以关联日志输出。

我使用 AutofacHostedServiceScopeOwnerMessageProcessingService包装在子作用域中。可能有更好的方法可以做到这一点,但这是我能想到的最好的方法。

public class AutofacHostedServiceScopeOwner : IHostedService, IDisposable
{
    private readonly Func<ILifetimeScope> _scopeFactory;
    private readonly Func<ILifetimeScope, IHostedService> _serviceFactory;
    private ILifetimeScope _currentScope;
    private IHostedService _hostedService;
    public AutofacHostedServiceScopeOwner(Func<ILifetimeScope> scopeFactory, Func<ILifetimeScope, IHostedService> serviceFactory) {
        _scopeFactory = scopeFactory;
        _serviceFactory = serviceFactory;
    }
    public async Task StartAsync(CancellationToken cancellationToken) {
        _currentScope = _scopeFactory();
        _hostedService = _serviceFactory(_currentScope);
        await _hostedService.StartAsync(cancellationToken);
    }
    public async Task StopAsync(CancellationToken cancellationToken) {
        if (_hostedService != null)
            await _hostedService.StopAsync(cancellationToken);
        _currentScope?.Dispose();
    }
    public void Dispose() {
        _currentScope?.Dispose();
    }
}

这一切都正常工作,但解析的记录器不包含预期的上下文属性。这让我认为我的覆盖逻辑是错误的,但我不确定还能尝试什么。

c.Resolve<ILifetimeScope>()
    .BeginLifetimeScope(
        b => b.Register(x => Log.Logger.ForContext("name", "value")).As<Serilog.ILogger>()
    )

appsettings.json

{
  "Serilog": {
    "Enrich": [ "FromLogContext" ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "Console"        
      }
    ]
  }
}

经过几个小时的头撞墙,我能够弄清楚这一点。

builder.Register(
        ctx => {
            var c = ctx.Resolve<IComponentContext>();
            return new AutofacHostedServiceScopeOwner(
                () => c.Resolve<ILifetimeScope>()
                    .BeginLifetimeScope(
                        b => {
                            b.RegisterGeneric(typeof(Logger<>)).As(typeof(ILogger<>));
                            b.Register(
                                    x => new SerilogLoggerFactory(
                                        Log.Logger.ForContext("name", "value")
                                    )
                                )
                                .As<ILoggerFactory>()
                                .InstancePerLifetimeScope();
                        }
                    ),
                scope => new MessageProcessingService(
                    scope.Resolve<ILogger<MessageProcessingService>>()
                )
            );
        }
    )
    .As<IHostedService>();

这样做的原因是,在Program.cs记录器配置调用中,sc.AddLogging(lb => lb.AddSerilog())ILogger<>ILoggerFactory注册为幕后的单例。lb.AddSerilog()调用注册了一个由ILoggerFactory使用的Serilog提供程序,但是我找不到任何方法来专门覆盖提供程序,因此我将它们全部替换。

希望这对某人有所帮助。

最新更新