question

ThomasArdal-9571 avatar image
0 Votes"
ThomasArdal-9571 asked ·

Implementing a logger provider for Blazor WASM

This is a cross-post of this question but I still miss the last pieces I believe: https://github.com/dotnet/aspnetcore/discussions/24697



I'm trying to implement a logger provider for Blazor WASM. The provider requires a HTTP client to communicate with a remote API. The Blazor WASM documentation for adding new providers is very short and limited. Basically, it requires you to call:

builder.Logging.AddProvider(new CustomLoggingProvider());


In real life, you probably don't want to configure providers this way, since they need options and dependencies.

I have tried implementing my own custom logger provider for Blazor WASM, but I'm not sure if I'm missing something. As mentioned, it requires a HTTP client but also some options. Options class first:

public class ElmahIoBlazorOptions
{
    public string ApiKey { get; set; }
    public Guid LogId { get; set; }
}


Properties don't really matter for this example. Then an extension method on ILoggingBuilder to configure the new provider:

public static class LoggingBuilderElmahIoExtensions
{
    public static ILoggingBuilder AddElmahIo(this ILoggingBuilder loggingBuilder, Action<ElmahIoBlazorOptions> configure)
    {
        loggingBuilder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri("https://api.elmah.io") });
        loggingBuilder.Services.Configure(configure);
        loggingBuilder.Services.AddSingleton<ILoggerProvider, ElmahIoLoggerProvider>(services =>
        {
            var httpClient = services.GetService<HttpClient>();
            var options = services.GetService<IOptions<ElmahIoBlazorOptions>>();
            return new ElmahIoLoggerProvider(httpClient, options);
        });
        return loggingBuilder;
    }
}


The method register a new HttpClient, configure the options, and add the new logger provider as a singleton.

The ILoggerProvider class looks like this:

public class ElmahIoLoggerProvider : ILoggerProvider
{
    private readonly HttpClient httpClient;
    private readonly ElmahIoBlazorOptions options;

    public ElmahIoLoggerProvider(HttpClient httpClient, IOptions<ElmahIoBlazorOptions> options)
    {
        this.httpClient = httpClient;
        this.options = options.Value;
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new ElmahIoLogger(httpClient, options);
    }

    public void Dispose()
    {
    }
}


And finally the logger:

public class ElmahIoLogger : ILogger
{
    private readonly HttpClient httpClient;
    private readonly ElmahIoBlazorOptions options;

    public ElmahIoLogger(HttpClient httpClient, ElmahIoBlazorOptions options)
    {
        this.httpClient = httpClient;
        this.options = options;
    }

    public IDisposable BeginScope<TState>(TState state) => null;

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        var baseException = exception?.GetBaseException();

        httpClient.PostAsJsonAsync(
            $"https://api.elmah.io/v3/messages/{options.LogId}?api_key={options.ApiKey}",
            new
            {
                dateTime = DateTime.UtcNow,
                detail = exception.StackTrace,
                type = baseException?.GetType().FullName,
                title = formatter(state, exception),
                severity = LogLevelToSeverity(logLevel),
                source = baseException?.Source,
                hostname = Environment.MachineName
            });
    }

    private string LogLevelToSeverity(LogLevel logLevel)
    {
        // impl left out
        return "Error";
    }
}


The new logger can now be configured like this:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("app");

        builder.Logging.AddElmahIo(options =>
        {
            options.ApiKey = "my api key";
            options.LogId = new Guid("my log id");
        });

        await builder.Build().RunAsync();
    }
}


Would this be a good way to implement a logger provider for Blazor WASM? I'm mostly thinking about the way I register the HTTP client and that the approach doesn't follow the documentation. Also, it would be great with some "official" examples of how to achieve this 👍

dotnet-aspnetcore-blazor
· 3
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi @ThomasArdal-9571, By using above sample code, you will create a Http Client for each log, it might cause performance issue if there have lots of logs. To improve performance, we could store the logs in cache or somewhere, then using background task/worker to create Http Client and store the logs.


0 Votes 0 ·

I think you are right there. What I have ended up doing in the code is this:

loggingBuilder.Services.AddSingleton<ILoggerProvider, ElmahIoLoggerProvider>(services =>
{
    var httpClient = new HttpClient { BaseAddress = new Uri("https://api.elmah.io") };
    var options = services.GetService<IOptions<ElmahIoBlazorOptions>>();
    return new ElmahIoLoggerProvider(httpClient, options);
});


I just don't like creating the HttpClient manually. But I guess that may be the best option here to avoid re-using the one already registered as part of the template when creating a new Blazor WASM app.

0 Votes 0 ·

I was just thinking that this is much more like the way you would do it in ASP.NET Core:

loggingBuilder.Services.AddHttpClient("elmah.io", x => x.BaseAddress = new Uri("https://api.elmah.io"));
loggingBuilder.Services.AddSingleton<ILoggerProvider, ElmahIoLoggerProvider>(services =>
{
    var httpClientFactory = services.GetService<IHttpClientFactory>();
    var httpClient = httpClientFactory.CreateClient("elmah.io");
    var options = services.GetService<IOptions<ElmahIoBlazorOptions>>();
    return new ElmahIoLoggerProvider(httpClient, options);
});

But using IHttpClientFactory in Blazor Wasm causes a runtime error for some reason. Even though I installed the Microsoft.Extensions.Http package. I guess that doesn't work with Blazor yet.

0 Votes 0 ·

0 Answers