Log levels in production
In this article, I will be looking back at my post about changing the log level from 2017. But this time, it will not be a full-featured platform like .NET Core 3.1 – but rather a browser with Blazor WebAssembly and .NET Standard code. The reason I use this feature is still the same as in 2017: By default, my applications usually have a log level of Information. If there is an issue in my code, I want to know the reason for it and then I need to lower the log level to get more information without rebuilding or restarting the application – in other words. reload the SPA. The main reason a reload of the app is undesirable is that the issue (which may be hard to reproduce) may just disappear.
Available log levels when using the Microsoft logging framework:
public enum LogLevel
{
Trace,
Debug,
Information,
Warning,
Error,
Critical,
None
}
Version information
.NET Core SDK: 3.1.302
ASP.NET Core Blazor WebAssembly: 3.2.1
The mentioned blog post from 2017 is addressing two logging frameworks, the Microsoft.Extensions.Logging and Serilog. This article here focuses on Microsoft.Extensions.Logging for simplicity reasons.
Demo application
To get started right away, I use the demo application that comes with the SDK. For that, we create a new Blazor WebAssembly project and make sure it works properly.
dotnet new blazorwasm -o BlazorApp1
cd BlazorApp1
dotnet run
For the verification of the feature, we are going to implement a means to create a few logs. Let us put a new button on the page Index.razor
which creates logs on a click.
@page "/"
@using Microsoft.Extensions.Logging
@inject ILogger Logger
@functions {
private void CreateLogs()
{
var logLevels = Enum.GetValues(typeof(LogLevel)).Cast();
foreach (var logLevel in logLevels.Where(l => l != LogLevel.None))
{
Logger.Log(logLevel, logLevel.ToString());
}
}
}
After starting the app and performing a click on the button, we should see some logs in the browser console.
There are 2 interesting points worth mentioning:
- The
WebAssemblyHostBuilder
in Blazor WebAssembly registers a logging provider (WebAssemblyConsoleLoggerProvider
) which logs onto the browser console - The default log level is
Information
(otherwise we would see the logsDebug
andTrace
)
Binding the log level to IConfiguration (a precondition for what comes next)
By default, there is no other way to change the log level besides hard-coding it using the WebAssemblyHostBuilder
.
// Program.cs
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Logging.SetMinimumLevel(LogLevel.Debug);
...
}
But we won’t do that. Instead, we bind the logger to the IConfiguration
using the extension method AddConfiguration
from the Nuget package Microsoft.Extensions.Logging.Configuration
.
// Program.cs
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
ConfigureLogging(builder);
...
}
private static void ConfigureLogging(
WebAssemblyHostBuilder builder,
string section = "Logging")
{
builder.Logging.AddConfiguration(builder.Configuration.GetSection(section));
}
To verify the results, we create a new file appsettings.json
in the folder wwwroot
, which changes the log level to Debug
.
{
"Logging": {
"LogLevel": {
"Default": "Debug"
}
}
}
By switching the log level to Debug
, we see our logs as well as the ones from Blazor.
Changing log level at runtime
For changing the log level at runtime, we need an implementation of a ConfigurationProvider
that adds/changes the configuration values in memory. To save our customers (and me) some time, I made a few Nuget packages that provide the required components. In our demo we need Thinktecture.Extensions.Logging.Configuration
which is made for Microsoft.Extensions.Logging
.
Furthermore, we have to register the LoggingConfiguration
with the current configuration builder and add it to the dependency injection (DI).
private static void ConfigureLogging(
WebAssemblyHostBuilder builder,
string section = "Logging")
{
var loggingConfig = new LoggingConfiguration();
builder.Services.AddSingleton(loggingConfig);
builder.Configuration.AddLoggingConfiguration(loggingConfig, section);
builder.Logging.AddConfiguration(builder.Configuration.GetSection(section));
}
After adding the LoggingConfiguration
to DI, we can inject it anywhere in the app using the interface ILoggingConfiguration
.
Next, we create a UI component to be able to change the log level at runtime. For that, we create a new file LogLevelOverride.razor
in the folder Shared
with the following content.
@using Thinktecture.Extensions.Configuration
@using Microsoft.Extensions.Logging
@inject ILoggingConfiguration Config
@inject ILogger Logger
@code{
private void ChangeLogLevel(ChangeEventArgs obj)
{
if (Enum.TryParse(obj.Value?.ToString(), out LogLevel logLevel))
{
Config.SetLevel(logLevel);
}
else
{
Config.ResetLevel();
}
}
}
The last step is to finally add the component to the page Index.razor
and to try it out.
@page "/"
@using Microsoft.Extensions.Logging
@inject ILogger Logger
...
Now we can set and reset (to default specified in appsettings.json
) the log level at any time.
Summary
In this article, we saw how to change the log level of a Blazor WebAssembly application at runtime to get more (or less) information out of the application. A temporal change of the log level for debugging purposes is just one step in making the software development and maintenance easier. The next step could be a diagnostics page with more tools and statistics to get even more data out of the SPA.