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 👍
I think you are right there. What I have ended up doing in the code is this:
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.
I was just thinking that this is much more like the way you would do it in ASP.NET Core:
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.