如何覆盖 Autofac 子范围中的 Serilog 记录器?
How to override Serilog logger in Autofac child scope?
我有一个使用 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 子作用域中 运行 MessageProcessingService
(一个 IHostedService
实现),这样我就可以覆盖已解析的整个 MessageProcessingService
图表中的记录器,其中包含一些特定于上下文的信息。将有多个 MessageProcessingService
个实例,每个实例都需要一个具有唯一上下文的记录器,以便我可以关联日志输出。
我使用 AutofacHostedServiceScopeOwner
将 MessageProcessingService
包装在子作用域中。可能有更好的方法来做到这一点,但这是我能想到的最好的方法。
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 提供程序,但我无法找到任何方法来专门覆盖提供程序,所以我将它们全部替换了。
希望这对某人有所帮助。
我有一个使用 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 子作用域中 运行 MessageProcessingService
(一个 IHostedService
实现),这样我就可以覆盖已解析的整个 MessageProcessingService
图表中的记录器,其中包含一些特定于上下文的信息。将有多个 MessageProcessingService
个实例,每个实例都需要一个具有唯一上下文的记录器,以便我可以关联日志输出。
我使用 AutofacHostedServiceScopeOwner
将 MessageProcessingService
包装在子作用域中。可能有更好的方法来做到这一点,但这是我能想到的最好的方法。
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 提供程序,但我无法找到任何方法来专门覆盖提供程序,所以我将它们全部替换了。
希望这对某人有所帮助。