简体   繁体   English

如何实现 ILogger 将消息发送到 SignalR 集线器?

[英]How to implement an ILogger to send messages to a SignalR Hub?

I want to build a LogView which shows the latest log messages.我想构建一个显示最新日志消息的 LogView。 So I've built a really simple setup but I fail at Dependency Injection.所以我建立了一个非常简单的设置,但我在依赖注入失败了。

Here's my attempt at an implementation.这是我对实现的尝试。 I've skipped the non-crucial parts.我跳过了非关键部分。

public class SignalRLogger : ILogger
{
    private readonly IHubContext<LogHub> _hub;

    public SignalRLogger(IHubContext<LogHub> hub)
    {
        _hub = hub;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        var msg = $"[{logLevel}] {formatter(state, exception)}";

        _hub.Clients.All.SendAsync("ReceiveMessage", msg);
    }
}

public class SignalrRLoggerProvider : ILoggerProvider
{
    private SignalRLogger _logger;

    public void Dispose()
    {
        _logger = null;
    }

    public ILogger CreateLogger(string categoryName)
    {
        if (_logger == null)
        {
            _logger = new SignalRLogger(????);
        }

        return _logger;
    }
}

My problem basically is that I can't inject an IHubContext and I'm not sure how to solve this issue我的问题基本上是我无法注入IHubContext ,我不知道如何解决这个问题

The problem is that the LoggerProvider is created before SignalR hubs are registered.问题是LoggerProvider是在注册SignalR集线器之前创建的。 So when the logger provider is created the IServiceProvider has not been initialized to know about any IHubContext<T> objects.因此,在创建记录器提供程序时,尚未初始化IServiceProvider以了解任何IHubContext<T>对象。

The only way I can find to solve it is to provide the IServiceProvider to your ILogger and let it get an instance of the IHubContext when it needs it.我能找到解决它的唯一方法是将IServiceProvider提供给您的ILogger并让它在需要时获取IHubContext的实例。

Your logger class needs to accept an IServiceProvider in it's constructor and then use that object to retrieve the IHubContext on demand:您的记录器类需要在其构造函数中接受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);
        }
    }
}

We need to check that the IHubContext has been retrieved from the service provider as the logger can be called before the hub has been registered.我们需要检查IHubContext是否已从服务提供者处检索到,因为可以在注册集线器之前调用记录器。 And this is one of the downsides to using SignalR as a logger because you will miss some early messages before the HubContext can be accessed (but this may be acceptable to you).这是使用 SignalR 作为记录器的缺点之一,因为在可以访问HubContext之前您会错过一些早期消息(但这对您来说可能是可以接受的)。

NOTE: You can improve this to store the hub instead of getting it on every log call - but I'll leave that for you to implement :-)注意:您可以改进它以存储集线器,而不是在每次日志调用时都获取它-但我将其留给您实现:-)

Now you need to change your provider to supply that parameter when it creates the logger:现在,您需要更改提供程序以在创建记录器时提供该参数:

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

Finally you need to provider the IServiceProvider to the logger provider:最后,您需要将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));
        });

We need to call the BuildServiceProvider method to create the interface we need as it's not available in ConfigureLogging call - but it will give us the correct interface to use.我们需要调用BuildServiceProvider方法来创建我们需要的接口,因为它在ConfigureLogging调用中不可用 - 但它会给我们正确的接口来使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM