带有 MassTransit - IBus 服务解析的 Serilog Sink 会导致无限循环



我正在尝试为Serilog设置一个接收器,该接收器通过MassTransit发布日志消息。 当我尝试在启动时创建接收器时,我遇到了问题。接收器创建会导致无限循环。

我应该如何注册依赖注入来解决这个循环?

我认为这是因为MassTransit.IBus使用ILogger。因此,当创建记录器时,它会要求我与 IBus 的接收器,IBus 请求一个记录器,记录器要求 IBus,等等。

程序中的启动代码.cs

//Code that sets up my mass transit. IBus is registered as part of it.
builder.Host.SetupMassTransit(); 
builder.Host.UseSerilog((ctx, services, lc) =>
{
lc.WriteTo.Sink(new MassTransitSink(services.GetService<IBus>())); //Set a breakpoint here
});

lc.WriteTo.Sink(...上设置断点,您将看到它被重复调用。

大众交通汇

public class MassTransitSink : ILogEventSink
{
private readonly IBus bus;
public MassTransitSink(IBus bus)
{
this.bus = bus;
}
public async void Emit(LogEvent logEvent)
{
await bus.Publish<MyLog>(new MyLog("Hello World"));
}
}
public class MyLog
{
public MyLog(string message)
{
Message = message;
}
public string Message { get; set; } 
}

这是一个解决方法,不允许您使用Microsoft的 ILogger,接受的答案是允许您使用 Serilog.ILogger 和 Microsoft.Extension.Logging.ILogger 的修复

程序我能够通过手动将Serilog.ILogger实例添加到依赖项容器来解决此问题。

替换program.csUseSerilog部分:

builder.Host.UseSerilog((ctx, services, lc) =>
{
lc.WriteTo.Sink(new MassTransitSink(services.GetService<IBus>()));
});

使用代码手动添加ILogger实例。

builder.Host.ConfigureServices(services =>
{
services.AddSingleton<Serilog.ILogger>(sp =>
{
var bus = sp.GetRequiredService<IBus>();
return new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Sink(new MassTransitSink(bus))
.CreateLogger();
});
services.AddLogging(configure => configure.ClearProviders().AddSerilog());
});

Serilog.ILogger现在可以在调试器中工作。但是Microsoft.Extensions.Logging.ILogger不起作用,即使当安装程序使用.UseSerilog()时它通常可用。

以为我会在这里发布这个,以防其他人遇到同样的问题。我花了几个小时才弄清楚问题所在。

有人知道一个更具可读性和/或Microsoft.Extensions.Logging.ILoggerSerilog.ILogger可用的解决方案吗?

问题是ILoggerIBus之间的周期性引用。因为我们的ILogger是通过带有接收器的serilog解决的,所以它需要一个IBus,这需要一个ILogger,重复。要修复它,请注册一个可以为 MassTransit 提供不依赖于IBusILoggerILoggerFactory

//Under the hood, this registers an ILoggerFactory
builder.UseSerilog((context, services, lc) =>
{
lc.WriteTo.Sink(new MassTransitSink(services.GetService<IBus>()));
});
builder.ConfigureServices((context, services) =>
{
//Register an ILoggerFactory to replace the one serilog registers when calling builder.UseSerilog
//This ILoggerFactory must provider MassTransit with an ILogger that is not dependent on an IBus.
services.AddSingleton<ILoggerFactory, WorkAroundLoggerFactory>();
});

为什么有效:
在引擎盖下,serilog 注册了一个SerilogLoggerFactory,它实现了ILoggerFactory。在运行时,ILoggerFactory被解析并用于创建任何ILogger。因此,我们可以注册一个替换ILoggerFactory,该查找为公共交通请求的 ILoggers 并提供一个不依赖于IBus,避免循环引用。

这是我的替代ILoggerFactory,WorkAroundLoggerFactory的示例。 因为我仍然希望Serilog为我的ILoggers提供标准配置,所以WorkAroundLoggerFactory使用SerilogLoggerFactory。为了让DI容器找到SerilogLoggerFactory,我必须在调用builder.UseSerilog(...)之前注册类型SerilogLoggerFactory

程序中的启动代码.cs

//Code that sets up my mass transit. IBus is registered as part of it.
builder.Host.SetupMassTransit(); 
builder.ConfigureServices((context, services) =>
{
//If you want to use the SerilogLoggerFactory in your work around factory, you need to tell the DI container to watch for it, before it is registered as part of UseSerilog
services.AddSingleton<SerilogLoggerFactory>();
});
builder.UseSerilog((context, services, lc) =>
{
lc.WriteTo.Sink(new MassTransitSink(services.GetService<IBus>()));
});
builder.ConfigureServices((context, services) =>
{
//Register the replacement ILoggerFactory
services.AddSingleton<ILoggerFactory, WorkAroundLoggerFactory>();
});

变通记录器.cs

// A logger factory that uses the SerilogLoggerFactory for anything but MassTransit logs.
public class WorkAroundLoggerFactory : ILoggerFactory
{
private readonly IServiceProvider serviceProvider;
private SerilogLoggerFactory? serilogLoggerFactory;
public WorkAroundLoggerFactory(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}

void ILoggerFactory.AddProvider(ILoggerProvider provider)
{
serilogLoggerFactory!.AddProvider(provider);
}
ILogger ILoggerFactory.CreateLogger(string categoryName)
{
if (categoryName.StartsWith("MassTransit"))
{
//Return an ILogger that does not use MassTransit for logging any MassTransit logs.
//If you wanted to log to windows event viewer, you could use EventLogLoggerProvider().CreateLogger(categoryName);
return new MyLogger(); 
}
else
{
if (serilogLoggerFactory is null)
{
//We can't resolve the SerilogILoggerFactory at construction because it will cause a loop, because it will think it needs an IBus, which needs an ILogger, which needs an IBus, ... etc
serilogLoggerFactory = serviceProvider.GetService(typeof(SerilogLoggerFactory)) as SerilogLoggerFactory;
if (serilogLoggerFactory is null)
{
throw new NullReferenceException(nameof(SerilogLoggerFactory));
}
}
return serilogLoggerFactory.CreateLogger(categoryName);
}
}
void IDisposable.Dispose()
{
if (serilogLoggerFactory != null)
{
serilogLoggerFactory.Dispose();
}
}
}

最新更新