Как переопределить регистратор Serilog в дочерней области Autofac?

2

У меня есть основной .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 (реализацию IHostedService) в дочерней области Autofac, чтобы я мог переопределить разрешенный регистратор во всем графе MessageProcessingService с некоторой контекстно-зависимой информацией. Там будет несколько экземпляров MessageProcessingService и каждому требуется регистратор с уникальным контекстом, чтобы я мог соотнести выходные данные журнала.

Я использую AutofacHostedServiceScopeOwner чтобы обернуть MessageProcessingService в AutofacHostedServiceScopeOwner область. Возможно, есть лучший способ сделать это, но это лучшее, что я мог придумать.

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"        
      }
    ]
  }
}
  • 0
    Замена всей фабрики лесозаготовителей кажется дорогой. Вы рассматривали пользовательские обогащатели вместо этого?
  • 0
    @TravisIllig Я обдумал это, но не знал, как применить обогащение в одной области. Я также попробовал LogContext.PushProperty, но не смог понять это.
Теги:
.net-core
autofac
serilog

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() регистрирует провайдера Serilog, который используется ILoggerFactory, но мне не удалось найти какой-либо способ переопределить провайдера специально, поэтому я заменил их всех.

Надеюсь, это кому-нибудь поможет.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню