【问题标题】:How to override Serilog logger in Autofac child scope?如何在 Autofac 子范围中覆盖 Serilog 记录器?
【发布时间】:2019-04-20 07:15:24
【问题描述】:

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

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 子范围内运行 MessageProcessingServiceIHostedService 实现),以便我可以在整个 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"        
      }
    ]
  }
}

【问题讨论】:

  • 更换整个记录器工厂似乎很昂贵。您是否考虑过custom enrichers
  • @TravisIllig 我考虑过,但不确定如何将浓缩器应用于单个范围。我也尝试了 LogContext.PushProperty,也无法弄清楚。

标签: c# .net-core autofac serilog asp.net-core-hosted-services


【解决方案1】:

经过数小时的头撞墙后,我终于弄明白了。

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 =&gt; lb.AddSerilog())ILogger&lt;&gt;ILoggerFactory 注册为单例。 lb.AddSerilog() 调用注册了一个由 ILoggerFactory 使用的 Serilog 提供程序,但我找不到任何方法来专门覆盖提供程序,所以我全部替换了它们。

希望这对某人有所帮助。

【讨论】:

    猜你喜欢
    • 2018-07-20
    • 2018-02-13
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-17
    相关资源
    最近更新 更多