Implementing a logger provider for Blazor WASM

Anonymous
2020-11-30T13:07:48.387+00:00

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 👍

Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,373 questions
{count} votes