如何在Autofac子范围中覆盖Serilog记录器?

问题描述 投票:2回答:1

我有一个使用Serilog和Autofac的基本.NET核心通用主机。

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>();
    }
}

这部分看起来很复杂,但我想要完成的是在Autofac子范围中运行MessageProcessingService(一个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"        
      }
    ]
  }
}
c# .net-core autofac serilog asp.net-core-hosted-services
1个回答
0
投票

经过几个小时的撞击我的头后,我能够弄明白这一点。

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提供程序,但是我无法找到任何方法来专门覆盖提供程序,所以我将它们全部替换掉​​了。

希望这有助于某人。

© www.soinside.com 2019 - 2024. All rights reserved.