2

I have a basic .NET Core Generic Host that uses Serilog and 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 is an Autofac module where I register a 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>();
    }
}

This part looks complicated, but what I am trying to accomplish is to run MessageProcessingService (an IHostedService implementation) in an Autofac child scope so that I can override the resolved logger within the entire MessageProcessingService graph with some context specific information. There will be multiple MessageProcessingService instances and each requires a logger with unique context so that I can correlate log output.

I use AutofacHostedServiceScopeOwner to wrap MessageProcessingService in a child scope. There is probably a better way to do this, but it's the best I could come up with.

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

This is all working correctly, except the resolved logger does not contain the expected context properties. This makes me think that my override logic is wrong, but I'm not sure what else to try.

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"        
      }
    ]
  }
}
4

1 回答 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.

希望这可以帮助某人。

于 2019-04-19T23:15:24.743 回答