简体   繁体   中英

How to implement an ILogger to send messages to a SignalR Hub?

I want to build a LogView which shows the latest log messages. 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

The problem is that the LoggerProvider is created before SignalR hubs are registered. So when the logger provider is created the IServiceProvider has not been initialized to know about any IHubContext<T> objects.

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.

Your logger class needs to accept an IServiceProvider in it's constructor and then use that object to retrieve the IHubContext on demand:

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. 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).

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:

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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