问题是LoggerProvider
之前创建的SignalR
集线器已注册。因此,当创建记录器提供程序时IServiceProvider
尚未初始化以了解任何信息IHubContext<T>
对象。
我能找到解决这个问题的唯一方法是提供IServiceProvider
给你的ILogger
并让它获得一个实例IHubContext
当它需要的时候。
您的记录器类需要接受IServiceProvider
在它的构造函数中,然后使用该对象来检索IHubContext
一经请求:
public class SignalRLogger : ILogger
{
private readonly IServiceProvider _sp;
public SignalRLogger(IServiceProvider sp)
{
_sp = sp;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var hub = _sp.GetService<IHubContext<LogHub>>();
if (hub != null)
{
var msg = $"[{logLevel}] {formatter(state, exception)}";
hub.Clients.All.SendAsync("ReceiveMessage", msg);
}
}
}
我们需要检查IHubContext
已从服务提供商处检索,因为可以在注册集线器之前调用记录器。这是使用 SignalR 作为记录器的缺点之一,因为您会错过一些早期消息HubContext
可以访问(但这可能对您来说是可以接受的)。
注意:您可以改进它来存储集线器,而不是在每次日志调用时获取它 - 但我将把它留给您来实现:-)
现在您需要更改您的提供程序以在创建记录器时提供该参数:
public class SignalrRLoggerProvider : ILoggerProvider
{
private SignalRLogger _logger;
private readonly IServiceProvider _sp;
public SignalrRLoggerProvider(IServiceProvider sp)
{
_sp = sp;
}
public ILogger CreateLogger(string categoryName)
{
if (_logger == null)
{
_logger = new SignalRLogger(_sp);
}
return _logger;
}
}
最后你需要提供IServiceProvider
记录器提供者:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(builder =>
{
var sp = builder.Services.BuildServiceProvider();
builder.AddProvider(new SignalrRLoggerProvider(sp));
});
我们需要调用BuildServiceProvider
方法来创建我们需要的接口,因为它在ConfigureLogging
call - 但它会给我们正确的接口来使用。