ASP.NET Core Blazor logging

This article explains logging in Blazor apps, including configuration and how to write log messages from Razor components.

Configuration

Logging configuration can be loaded from app settings files. For more information, see ASP.NET Core Blazor configuration.

At default log levels and without configuring additional logging providers:

When the app is configured in the project file to use implicit namespaces (<ImplicitUsings>enable</ImplicitUsings>), a using directive for Microsoft.Extensions.Logging or any API in the LoggerExtensions class isn't required to support API Visual Studio IntelliSense completions or building apps. If implicit namespaces aren't enabled, Razor components must explicitly define @using directives for logging namespaces that aren't imported via the _Imports.razor file.

Log levels

Log levels in Blazor apps conform to ASP.NET Core app log levels, which are listed in the API documentation at LogLevel.

Razor component logging

The following example:

  • Injects an ILogger (ILogger<Counter>) object to create a logger. The log's category is the fully qualified name of the component's type, Counter.
  • Calls LogWarning to log at the Warning level.

Pages/Counter1.razor:

@page "/counter-1"
@inject ILogger<Counter> logger

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

The following example demonstrates logging with an ILoggerFactory in components.

Pages/Counter2.razor:

@page "/counter-2"
@inject ILoggerFactory LoggerFactory

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        var logger = LoggerFactory.CreateLogger<Counter>();
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

Logging in Blazor Server apps

For general ASP.NET Core logging guidance that pertains to Blazor Server, see Logging in .NET Core and ASP.NET Core.

Logging in Blazor WebAssembly apps

Not every feature of ASP.NET Core logging is supported in Blazor WebAssembly apps. For example, Blazor WebAssembly apps don't have access to the client's file system or network, so writing logs to the client's physical or network storage isn't possible. When using a third-party logging service designed to work with single-page apps (SPAs), follow the service's security guidance. Keep in mind that every piece of data, including keys or secrets stored in the Blazor WebAssembly app are insecure and can be easily discovered by malicious users.

Configure logging in Blazor WebAssembly apps with the WebAssemblyHostBuilder.Logging property. The Logging property is of type ILoggingBuilder, so the extension methods of ILoggingBuilder are supported.

To set the minimum logging level, call LoggingBuilderExtensions.SetMinimumLevel on the host builder in Program.cs with the LogLevel. The following example sets the minimum log level to Warning:

builder.Logging.SetMinimumLevel(LogLevel.Warning);

Log in Program.cs

Logging in Program.cs is supported in Blazor WebAssembly apps after the WebAssemblyHostBuilder is built using the framework's internal console logger provider (WebAssemblyConsoleLoggerProvider (reference source)).

In Program.cs:

var host = builder.Build();

var logger = host.Services.GetRequiredService<ILoggerFactory>()
    .CreateLogger<Program>();

logger.LogInformation("Logged after the app is built in Program.cs.");

await host.RunAsync();

Developer tools console output:

info: Program[0]
Logged after the app is built in Program.cs.

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Log category

Log categories are supported in Blazor WebAssembly apps.

The following example shows how to use log categories with the Counter component of an app created from a Blazor project template.

In the IncrementCount method of the app's Counter component (Pages/Counter.razor) that injects an ILoggerFactory as LoggerFactory:

var logger = LoggerFactory.CreateLogger("CustomCategory");
logger.LogWarning("Someone has clicked me!");

Developer tools console output:

warn: CustomCategory[0]
Someone has clicked me!

Log event ID

Log event ID is supported in Blazor WebAssembly apps.

The following example shows how to use log event IDs with the Counter component of an app created from a Blazor project template.

LogEvent.cs:

public class LogEvent
{
    public const int Event1 = 1000;
    public const int Event2 = 1001;
}

In the IncrementCount method of the app's Counter component (Pages/Counter.razor):

logger.LogInformation(LogEvent.Event1, "Someone has clicked me!");
logger.LogWarning(LogEvent.Event2, "Someone has clicked me!");

Developer tools console output:

info: BlazorSample.Pages.Counter[1000]
Someone has clicked me!
warn: BlazorSample.Pages.Counter[1001]
Someone has clicked me!

Log message template

Log message templates are supported in Blazor WebAssembly apps:

The following example shows how to use log message templates with the Counter component of an app created from a Blazor project template.

In the IncrementCount method of the app's Counter component (Pages/Counter.razor):

logger.LogInformation("Someone clicked me at {CurrentDT}!", DateTime.UtcNow);

Developer tools console output:

info: BlazorSample.Pages.Counter[0]
Someone clicked me at 04/21/2022 12:15:57!

Log exception parameters

Log exception parameters are supported in Blazor WebAssembly apps.

The following example shows how to use log exception parameters with the Counter component of an app created from a Blazor project template.

In the IncrementCount method of the app's Counter component (Pages/Counter.razor):

currentCount++;

try
{
    if (currentCount == 3)
    {
        currentCount = 4;
        throw new OperationCanceledException("Skip 3");
    }
}
catch (Exception ex)
{
    logger.LogWarning(ex, "Exception (currentCount: {Count})!", currentCount);
}

Developer tools console output:

warn: BlazorSample.Pages.Counter[0]
Exception (currentCount: 4)!
System.OperationCanceledException: Skip 3
at BlazorSample.Pages.Counter.IncrementCount() in C:UsersAlabaDesktopBlazorSamplePagesCounter.razor:line 28

Filter function

Filter functions are supported in Blazor WebAssembly apps.

The following example shows how to use a filter with the Counter component of an app created from a Blazor project template.

In Program.cs:

builder.Logging.AddFilter((provider, category, logLevel) =>
    category.Equals("CustomCategory2") && logLevel == LogLevel.Information);

In the IncrementCount method of the app's Counter component (Pages/Counter.razor) that injects an ILoggerFactory as LoggerFactory:

var logger1 = LoggerFactory.CreateLogger("CustomCategory1");
logger1.LogInformation("Someone has clicked me!");

var logger2 = LoggerFactory.CreateLogger("CustomCategory1");
logger2.LogWarning("Someone has clicked me!");

var logger3 = LoggerFactory.CreateLogger("CustomCategory2");
logger3.LogInformation("Someone has clicked me!");

var logger4 = LoggerFactory.CreateLogger("CustomCategory2");
logger4.LogWarning("Someone has clicked me!");

In the developer tools console output, the filter only permits logging for the CustomCategory2 category and Information log level message:

info: CustomCategory2[0]
Someone has clicked me!

The app can also configure log filtering for specific namespaces. For example, set the log level to Trace in Program.cs:

builder.Logging.SetMinimumLevel(LogLevel.Trace);

Normally at the Trace log level, developer tools console output at the Verbose level includes Microsoft.AspNetCore.Components.RenderTree logging messages, such as the following:

dbug: Microsoft.AspNetCore.Components.RenderTree.Renderer[3]
Rendering component 14 of type Microsoft.AspNetCore.Components.Web.HeadOutlet

In Program.cs, logging messages specific to Microsoft.AspNetCore.Components.RenderTree can be disabled using either of the following approaches:

  • builder.Logging.AddFilter("Microsoft.AspNetCore.Components.RenderTree.*", LogLevel.None);
    
  • builder.Services.PostConfigure<LoggerFilterOptions>(options =>
        options.Rules.Add(
            new LoggerFilterRule(null, 
                                 "Microsoft.AspNetCore.Components.RenderTree.*", 
                                 LogLevel.None, 
                                 null)
        ));
    

After either of the preceding filters is added to the app, the console output at the Verbose level doesn't show logging messages from the Microsoft.AspNetCore.Components.RenderTree API.

Custom logger provider

The example in this section demonstrates a custom logger provider for further customization.

Add a package reference to the app for the Microsoft.Extensions.Logging.Configuration package.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Add the following custom logger configuration. The configuration establishes a LogLevels dictionary that sets a custom log format for three log levels: Information, Warning, and Error. A LogFormat enum is used to describe short (LogFormat.Short) and long (LogFormat.Long) formats.

CustomLoggerConfiguration.cs:

using Microsoft.Extensions.Logging;

public class CustomLoggerConfiguration
{
    public int EventId { get; set; }

    public Dictionary<LogLevel, LogFormat> LogLevels { get; set; } = 
        new()
        {
            [LogLevel.Information] = LogFormat.Short,
            [LogLevel.Warning] = LogFormat.Short,
            [LogLevel.Error] = LogFormat.Long
        };

    public enum LogFormat
    {
        Short,
        Long
    }
}

Add the following custom logger to the app. The CustomLogger outputs custom log formats based on the logLevel values defined in the preceding CustomLoggerConfiguration configuration.

using Microsoft.Extensions.Logging;
using static CustomLoggerConfiguration;

public sealed class CustomLogger : ILogger
{
    private readonly string name;
    private readonly Func<CustomLoggerConfiguration> getCurrentConfig;

    public CustomLogger(
        string name,
        Func<CustomLoggerConfiguration> getCurrentConfig) =>
        (this.name, this.getCurrentConfig) = (name, getCurrentConfig);

    public IDisposable BeginScope<TState>(TState state) => default!;

    public bool IsEnabled(LogLevel logLevel) =>
        getCurrentConfig().LogLevels.ContainsKey(logLevel);

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        CustomLoggerConfiguration config = getCurrentConfig();

        if (config.EventId == 0 || config.EventId == eventId.Id)
        {
            switch (config.LogLevels[logLevel])
            {
                case LogFormat.Short:
                    Console.WriteLine($"{name}: {formatter(state, exception)}");
                    break;
                case LogFormat.Long:
                    Console.WriteLine($"[{eventId.Id, 2}: {logLevel, -12}] {name} - {formatter(state, exception)}");
                    break;
                default:
                    // No-op
                    break;
            }
        }
    }
}

Add the following custom logger provider to the app. CustomLoggerProvider adopts an Options-based approach to configure the logger via built-in logging configuration features. For example, the app can set or change log formats via an appsettings.json file without requiring code changes to the custom logger, which is demonstrated at the end of this section.

CustomLoggerProvider.cs:

using System.Collections.Concurrent;
using Microsoft.Extensions.Options;

[ProviderAlias("CustomLog")]
public sealed class CustomLoggerProvider : ILoggerProvider
{
    private readonly IDisposable onChangeToken;
    private CustomLoggerConfiguration config;
    private readonly ConcurrentDictionary<string, CustomLogger> loggers =
        new(StringComparer.OrdinalIgnoreCase);

    public CustomLoggerProvider(
        IOptionsMonitor<CustomLoggerConfiguration> config)
    {
        this.config = config.CurrentValue;
        onChangeToken = config.OnChange(updatedConfig => this.config = updatedConfig);
    }

    public ILogger CreateLogger(string categoryName) =>
        loggers.GetOrAdd(categoryName, name => new CustomLogger(name, GetCurrentConfig));

    private CustomLoggerConfiguration GetCurrentConfig() => config;

    public void Dispose()
    {
        loggers.Clear();
        onChangeToken.Dispose();
    }
}

Add the following custom logger extensions.

CustomLoggerExtensions.cs:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;

public static class CustomLoggerExtensions
{
    public static ILoggingBuilder AddCustomLogger(
        this ILoggingBuilder builder)
    {
        builder.AddConfiguration();

        builder.Services.TryAddEnumerable(
            ServiceDescriptor.Singleton<ILoggerProvider, CustomLoggerProvider>());

        LoggerProviderOptions.RegisterProviderOptions
            <CustomLoggerConfiguration, CustomLoggerProvider>(builder.Services);

        return builder;
    }
}

In Program.cs on the host builder, clear the existing provider by calling ClearProviders and add the custom logging provider:

builder.Logging.ClearProviders().AddCustomLogger();

In the following Index component:

  • The debug message isn't logged.
  • The information message is logged in the short format (LogFormat.Short).
  • The warning message is logged in the short format (LogFormat.Short).
  • The error message is logged in the long format (LogFormat.Long).
  • The trace message isn't logged.

Pages/Index.razor:

@page "/"
@using Microsoft.Extensions.Logging
@inject ILogger<Index> Logger

<p>
    <button @onclick="LogMessages">Log Messages</button>
</p>

@code{
    private void LogMessages()
    {
        Logger.LogDebug(1, "This is a debug message.");
        Logger.LogInformation(3, "This is an information message.");
        Logger.LogWarning(5, "This is a warning message.");
        Logger.LogError(7, "This is an error message.");
        Logger.LogTrace(5!, "This is a trace message.");
    }
}

The following output is seen in the browser's developer tools console when the Log Messages button is selected. The log entries reflect the appropriate formats applied by the custom logger:

LoggingTest.Pages.Index: This is an information message.
LoggingTest.Pages.Index: This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

From a casual inspection of the preceding example, it's apparent that setting the log line formats via the dictionary in CustomLoggerConfiguration isn't strictly necessary. The line formats applied by the custom logger (CustomLogger) could have been applied by merely checking the logLevel in the Log method. The purpose of assigning the log format via configuration is that the developer can change the log format easily via app configuration, as the following example demonstrates.

In the wwwroot folder, add or update the appsettings.json file to include logging configuration. Set the log format to Long for all three log levels:

{
  "Logging": {
    "CustomLog": {
      "LogLevels": {
        "Information": "Long",
        "Warning": "Long",
        "Error": "Long"
      }
    }
  }
}

In the preceding example, notice that the entry for the custom logger configuration is CustomLog, which was applied to the custom logger provider (CustomLoggerProvider) as an alias with [ProviderAlias("CustomLog")]. The logging configuration could have been applied with the name CustomLoggerProvider instead of CustomLog, but use of the alias CustomLog is more user friendly.

In Program.cs consume the logging configuration. Add the following code:

builder.Logging.AddConfiguration(
    builder.Configuration.GetSection("Logging"));

The call to LoggingBuilderConfigurationExtensions.AddConfiguration can be placed either before or after adding the custom logger provider.

Run the app again. Select the Log Messages button. Notice that the logging configuration is applied from the appsettings.json file. All three log entries are in the long (LogFormat.Long) format:

[ 3: Information ] LoggingTest.Pages.Index - This is an information message.
[ 5: Warning ] LoggingTest.Pages.Index - This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

Log scopes

The Blazor WebAssembly developer tools console logger doesn't support log scopes. However, a custom logger can support log scopes. For an unsupported example that you can further develop to suit your needs, see the prototype in the dotnet/blazor-samples GitHub repository:

BlazorWebAssemblyScopesLogger sample app

The sample app uses standard ASP.NET Core BeginScope logging syntax to indicate scopes for logged messages. The Logger service in the following example is an ILogger<Index>, which is injected into the app's Index component (Pages/Index.razor).

using (Logger.BeginScope("L1"))
{
    Logger.LogInformation(3, "INFO: ONE scope.");
}

using (Logger.BeginScope("L1"))
{
    using (Logger.BeginScope("L2"))
    {
        Logger.LogInformation(3, "INFO: TWO scopes.");
    }
}

using (Logger.BeginScope("L1"))
{
    using (Logger.BeginScope("L2"))
    {
        using (Logger.BeginScope("L3"))
        {
            Logger.LogInformation(3, "INFO: THREE scopes.");
        }
    }
}

Output:

[ 3: Information ] ScopesLogger.Pages.Index - INFO: ONE scope. => L1 blazor.webassembly.js:1:35542
[ 3: Information ] ScopesLogger.Pages.Index - INFO: TWO scopes. => L1 => L2 blazor.webassembly.js:1:35542
[ 3: Information ] ScopesLogger.Pages.Index - INFO: THREE scopes. => L1 => L2 => L3

Hosted Blazor WebAssembly logging

A hosted Blazor WebAssembly app that prerenders its content executes component initialization code twice. Logging takes place server-side on the first execution of initialization code and client-side on the second execution of initialization code. Depending on the goal of logging during initialization, check logs server-side, client-side, or both.

SignalR client logging (Blazor Server)

On the client builder in Pages/_Layout.cshtml, pass in the configureSignalR configuration object that calls configureLogging with the log level.

For the configureLogging log level value, pass the argument as either the string or integer log level shown in the following table.

LogLevel String setting Integer setting
Trace trace 0
Debug debug 1
Information information 2
Warning warning 3
Error error 4
Critical critical 5
None none 6

Example 1: Set the Information log level with a string value:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.configureLogging("information");
    }
  });
</script>

Example 2: Set the Information log level with an integer value:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.configureLogging(2);
    }
  });
</script>

For more information on Blazor startup (Blazor.start), see ASP.NET Core Blazor startup.

SignalR client logging (Blazor WebAssembly)

In Blazor WebAssembly apps, set up app settings configuration as described in ASP.NET Core Blazor configuration. Place app settings files in wwwroot that contain a Logging:LogLevel:HubConnection app setting.

Note

As an alternative to using app settings, you can pass the LogLevel as the argument to LoggingBuilderExtensions.SetMinimumLevel when the hub connection is created in a Razor component. However, accidentally deploying the app to a production hosting environment with verbose logging may result in a performance penalty. We recommend using app settings to set the log level.

Provide a Logging:LogLevel:HubConnection app setting in the default appsettings.json file and in the Development environment app settings file. Use a typical less-verbose log level for the default, such as LogLevel.Warning. The default app settings value is what is used in Staging and Production environments if no app settings files for those environments are present. Use a verbose log level in the Development environment app settings file, such as LogLevel.Trace.

wwwroot/appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "HubConnection": "Warning"
    }
  }
}

wwwroot/appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "HubConnection": "Trace"
    }
  }
}

Important

Configuration in the preceding app settings files is only used by the app if the guidance in ASP.NET Core Blazor configuration is followed.

At the top of the Razor component file (.razor):

  • Inject an ILoggerProvider to add a WebAssemblyConsoleLogger to the logging providers passed to HubConnectionBuilder. Unlike a traditional ConsoleLogger, WebAssemblyConsoleLogger is a wrapper around browser-specific logging APIs (for example, console.log). Use of WebAssemblyConsoleLogger makes logging possible within Mono inside a browser context.
  • Inject an IConfiguration to read the Logging:LogLevel:HubConnection app setting.

Note

WebAssemblyConsoleLogger is internal and not supported for direct use in developer code.

@inject ILoggerProvider LoggerProvider
@inject IConfiguration Config

Note

The following example is based on the Index component in the SignalR with Blazor tutorial. Consult the tutorial for further details.

In the component's OnInitializedAsync method, use HubConnectionBuilderExtensions.ConfigureLogging to add the logging provider and set the minimum log level from configuration:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .ConfigureLogging(builder => 
        {
            builder.AddProvider(LoggerProvider);
            builder.SetMinimumLevel(
                Config.GetValue<LogLevel>("Logging:LogLevel:HubConnection"));
        })
        .Build();

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Note

In the preceding example, Navigation is an injected NavigationManager.

For more information on setting the app's environment for Blazor WebAssembly, see ASP.NET Core Blazor environments.

Blazor WebAssembly authentication logging

This section only applies to Blazor WebAssembly apps.

Log Blazor authentication messages at the LogLevel.Debug or LogLevel.Trace logging levels with a logging configuration in app settings or by using a log filter for Microsoft.AspNetCore.Components.WebAssembly.Authentication in Program.cs.

Use either of the following approaches:

  • In an app settings file (for example, wwwroot/appsettings.Development.json):

    "Logging": {
      "LogLevel": {
        "Microsoft.AspNetCore.Components.WebAssembly.Authentication": "Debug"
      }
    }
    

    For more information on how to enable a Blazor WebAssembly app to read app settings files, see ASP.NET Core Blazor configuration.

  • Using a log filter, the following example:

    #if DEBUG
        builder.Logging.AddFilter(
            "Microsoft.AspNetCore.Components.WebAssembly.Authentication", 
            LogLevel.Debug);
    #endif
    

Note

Blazor WebAssembly apps only log to the client-side browser developer tools console.

Additional resources

Configuration

Logging configuration can be loaded from app settings files. For more information, see ASP.NET Core Blazor configuration.

At default log levels and without configuring additional logging providers:

When the app is configured in the project file to use implicit namespaces (<ImplicitUsings>enable</ImplicitUsings>), a using directive for Microsoft.Extensions.Logging or any API in the LoggerExtensions class isn't required to support API Visual Studio IntelliSense completions or building apps. If implicit namespaces aren't enabled, Razor components must explicitly define @using directives for logging namespaces that aren't imported via the _Imports.razor file.

Log levels

Log levels in Blazor apps conform to ASP.NET Core app log levels, which are listed in the API documentation at LogLevel.

Razor component logging

The following example:

  • Injects an ILogger (ILogger<Counter>) object to create a logger. The log's category is the fully qualified name of the component's type, Counter.
  • Calls LogWarning to log at the Warning level.

Pages/Counter1.razor:

@page "/counter-1"
@inject ILogger<Counter> logger

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

The following example demonstrates logging with an ILoggerFactory in components.

Pages/Counter2.razor:

@page "/counter-2"
@inject ILoggerFactory LoggerFactory

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        var logger = LoggerFactory.CreateLogger<Counter>();
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

Logging in Blazor Server apps

For general ASP.NET Core logging guidance that pertains to Blazor Server, see Logging in .NET Core and ASP.NET Core.

Logging in Blazor WebAssembly apps

Not every feature of ASP.NET Core logging is supported in Blazor WebAssembly apps. For example, Blazor WebAssembly apps don't have access to the client's file system or network, so writing logs to the client's physical or network storage isn't possible. When using a third-party logging service designed to work with single-page apps (SPAs), follow the service's security guidance. Keep in mind that every piece of data, including keys or secrets stored in the Blazor WebAssembly app are insecure and can be easily discovered by malicious users.

Configure logging in Blazor WebAssembly apps with the WebAssemblyHostBuilder.Logging property. The Logging property is of type ILoggingBuilder, so the extension methods of ILoggingBuilder are supported.

To set the minimum logging level, call LoggingBuilderExtensions.SetMinimumLevel on the host builder in Program.cs with the LogLevel. The following example sets the minimum log level to Warning:

builder.Logging.SetMinimumLevel(LogLevel.Warning);

Log in Program.cs

Logging in Program.cs is supported in Blazor WebAssembly apps after the WebAssemblyHostBuilder is built using the framework's internal console logger provider (WebAssemblyConsoleLoggerProvider (reference source)).

In Program.cs:

var host = builder.Build();

var logger = host.Services.GetRequiredService<ILoggerFactory>()
    .CreateLogger<Program>();

logger.LogInformation("Logged after the app is built in Program.cs.");

await host.RunAsync();

Developer tools console output:

info: Program[0]
Logged after the app is built in Program.cs.

Note

Documentation links to .NET reference source usually load the repository's default branch, which represents the current development for the next release of .NET. To select a tag for a specific release, use the Switch branches or tags dropdown list. For more information, see How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205).

Log category

Log categories are supported in Blazor WebAssembly apps.

The following example shows how to use log categories with the Counter component of an app created from a Blazor project template.

In the IncrementCount method of the app's Counter component (Pages/Counter.razor) that injects an ILoggerFactory as LoggerFactory:

var logger = LoggerFactory.CreateLogger("CustomCategory");
logger.LogWarning("Someone has clicked me!");

Developer tools console output:

warn: CustomCategory[0]
Someone has clicked me!

Log event ID

Log event ID is supported in Blazor WebAssembly apps.

The following example shows how to use log event IDs with the Counter component of an app created from a Blazor project template.

LogEvent.cs:

public class LogEvent
{
    public const int Event1 = 1000;
    public const int Event2 = 1001;
}

In the IncrementCount method of the app's Counter component (Pages/Counter.razor):

logger.LogInformation(LogEvent.Event1, "Someone has clicked me!");
logger.LogWarning(LogEvent.Event2, "Someone has clicked me!");

Developer tools console output:

info: BlazorSample.Pages.Counter[1000]
Someone has clicked me!
warn: BlazorSample.Pages.Counter[1001]
Someone has clicked me!

Log message template

Log message templates are supported in Blazor WebAssembly apps:

The following example shows how to use log message templates with the Counter component of an app created from a Blazor project template.

In the IncrementCount method of the app's Counter component (Pages/Counter.razor):

logger.LogInformation("Someone clicked me at {CurrentDT}!", DateTime.UtcNow);

Developer tools console output:

info: BlazorSample.Pages.Counter[0]
Someone clicked me at 04/21/2022 12:15:57!

Log exception parameters

Log exception parameters are supported in Blazor WebAssembly apps.

The following example shows how to use log exception parameters with the Counter component of an app created from a Blazor project template.

In the IncrementCount method of the app's Counter component (Pages/Counter.razor):

currentCount++;

try
{
    if (currentCount == 3)
    {
        currentCount = 4;
        throw new OperationCanceledException("Skip 3");
    }
}
catch (Exception ex)
{
    logger.LogWarning(ex, "Exception (currentCount: {Count})!", currentCount);
}

Developer tools console output:

warn: BlazorSample.Pages.Counter[0]
Exception (currentCount: 4)!
System.OperationCanceledException: Skip 3
at BlazorSample.Pages.Counter.IncrementCount() in C:UsersAlabaDesktopBlazorSamplePagesCounter.razor:line 28

Filter function

Filter functions are supported in Blazor WebAssembly apps.

The following example shows how to use a filter with the Counter component of an app created from a Blazor project template.

In Program.cs:

builder.Logging.AddFilter((provider, category, logLevel) =>
    category.Equals("CustomCategory2") && logLevel == LogLevel.Information);

In the IncrementCount method of the app's Counter component (Pages/Counter.razor) that injects an ILoggerFactory as LoggerFactory:

var logger1 = LoggerFactory.CreateLogger("CustomCategory1");
logger1.LogInformation("Someone has clicked me!");

var logger2 = LoggerFactory.CreateLogger("CustomCategory1");
logger2.LogWarning("Someone has clicked me!");

var logger3 = LoggerFactory.CreateLogger("CustomCategory2");
logger3.LogInformation("Someone has clicked me!");

var logger4 = LoggerFactory.CreateLogger("CustomCategory2");
logger4.LogWarning("Someone has clicked me!");

In the developer tools console output, the filter only permits logging for the CustomCategory2 category and Information log level message:

info: CustomCategory2[0]
Someone has clicked me!

The app can also configure log filtering for specific namespaces. For example, set the log level to Trace in Program.cs:

builder.Logging.SetMinimumLevel(LogLevel.Trace);

Normally at the Trace log level, developer tools console output at the Verbose level includes Microsoft.AspNetCore.Components.RenderTree logging messages, such as the following:

dbug: Microsoft.AspNetCore.Components.RenderTree.Renderer[3]
Rendering component 14 of type Microsoft.AspNetCore.Components.Web.HeadOutlet

In Program.cs, logging messages specific to Microsoft.AspNetCore.Components.RenderTree can be disabled using either of the following approaches:

  • builder.Logging.AddFilter("Microsoft.AspNetCore.Components.RenderTree.*", LogLevel.None);
    
  • builder.Services.PostConfigure<LoggerFilterOptions>(options =>
        options.Rules.Add(
            new LoggerFilterRule(null, 
                                 "Microsoft.AspNetCore.Components.RenderTree.*", 
                                 LogLevel.None, 
                                 null)
        ));
    

After either of the preceding filters is added to the app, the console output at the Verbose level doesn't show logging messages from the Microsoft.AspNetCore.Components.RenderTree API.

Custom logger provider

The example in this section demonstrates a custom logger provider for further customization.

Add a package reference to the app for the Microsoft.Extensions.Logging.Configuration package.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Add the following custom logger configuration. The configuration establishes a LogLevels dictionary that sets a custom log format for three log levels: Information, Warning, and Error. A LogFormat enum is used to describe short (LogFormat.Short) and long (LogFormat.Long) formats.

CustomLoggerConfiguration.cs:

using Microsoft.Extensions.Logging;

public class CustomLoggerConfiguration
{
    public int EventId { get; set; }

    public Dictionary<LogLevel, LogFormat> LogLevels { get; set; } = 
        new()
        {
            [LogLevel.Information] = LogFormat.Short,
            [LogLevel.Warning] = LogFormat.Short,
            [LogLevel.Error] = LogFormat.Long
        };

    public enum LogFormat
    {
        Short,
        Long
    }
}

Add the following custom logger to the app. The CustomLogger outputs custom log formats based on the logLevel values defined in the preceding CustomLoggerConfiguration configuration.

using Microsoft.Extensions.Logging;
using static CustomLoggerConfiguration;

public sealed class CustomLogger : ILogger
{
    private readonly string name;
    private readonly Func<CustomLoggerConfiguration> getCurrentConfig;

    public CustomLogger(
        string name,
        Func<CustomLoggerConfiguration> getCurrentConfig) =>
        (this.name, this.getCurrentConfig) = (name, getCurrentConfig);

    public IDisposable BeginScope<TState>(TState state) => default!;

    public bool IsEnabled(LogLevel logLevel) =>
        getCurrentConfig().LogLevels.ContainsKey(logLevel);

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        CustomLoggerConfiguration config = getCurrentConfig();

        if (config.EventId == 0 || config.EventId == eventId.Id)
        {
            switch (config.LogLevels[logLevel])
            {
                case LogFormat.Short:
                    Console.WriteLine($"{name}: {formatter(state, exception)}");
                    break;
                case LogFormat.Long:
                    Console.WriteLine($"[{eventId.Id, 2}: {logLevel, -12}] {name} - {formatter(state, exception)}");
                    break;
                default:
                    // No-op
                    break;
            }
        }
    }
}

Add the following custom logger provider to the app. CustomLoggerProvider adopts an Options-based approach to configure the logger via built-in logging configuration features. For example, the app can set or change log formats via an appsettings.json file without requiring code changes to the custom logger, which is demonstrated at the end of this section.

CustomLoggerProvider.cs:

using System.Collections.Concurrent;
using Microsoft.Extensions.Options;

[ProviderAlias("CustomLog")]
public sealed class CustomLoggerProvider : ILoggerProvider
{
    private readonly IDisposable onChangeToken;
    private CustomLoggerConfiguration config;
    private readonly ConcurrentDictionary<string, CustomLogger> loggers =
        new(StringComparer.OrdinalIgnoreCase);

    public CustomLoggerProvider(
        IOptionsMonitor<CustomLoggerConfiguration> config)
    {
        this.config = config.CurrentValue;
        onChangeToken = config.OnChange(updatedConfig => this.config = updatedConfig);
    }

    public ILogger CreateLogger(string categoryName) =>
        loggers.GetOrAdd(categoryName, name => new CustomLogger(name, GetCurrentConfig));

    private CustomLoggerConfiguration GetCurrentConfig() => config;

    public void Dispose()
    {
        loggers.Clear();
        onChangeToken.Dispose();
    }
}

Add the following custom logger extensions.

CustomLoggerExtensions.cs:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;

public static class CustomLoggerExtensions
{
    public static ILoggingBuilder AddCustomLogger(
        this ILoggingBuilder builder)
    {
        builder.AddConfiguration();

        builder.Services.TryAddEnumerable(
            ServiceDescriptor.Singleton<ILoggerProvider, CustomLoggerProvider>());

        LoggerProviderOptions.RegisterProviderOptions
            <CustomLoggerConfiguration, CustomLoggerProvider>(builder.Services);

        return builder;
    }
}

In Program.cs on the host builder, clear the existing provider by calling ClearProviders and add the custom logging provider:

builder.Logging.ClearProviders().AddCustomLogger();

In the following Index component:

  • The debug message isn't logged.
  • The information message is logged in the short format (LogFormat.Short).
  • The warning message is logged in the short format (LogFormat.Short).
  • The error message is logged in the long format (LogFormat.Long).
  • The trace message isn't logged.

Pages/Index.razor:

@page "/"
@using Microsoft.Extensions.Logging
@inject ILogger<Index> Logger

<p>
    <button @onclick="LogMessages">Log Messages</button>
</p>

@code{
    private void LogMessages()
    {
        Logger.LogDebug(1, "This is a debug message.");
        Logger.LogInformation(3, "This is an information message.");
        Logger.LogWarning(5, "This is a warning message.");
        Logger.LogError(7, "This is an error message.");
        Logger.LogTrace(5!, "This is a trace message.");
    }
}

The following output is seen in the browser's developer tools console when the Log Messages button is selected. The log entries reflect the appropriate formats applied by the custom logger:

LoggingTest.Pages.Index: This is an information message.
LoggingTest.Pages.Index: This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

From a casual inspection of the preceding example, it's apparent that setting the log line formats via the dictionary in CustomLoggerConfiguration isn't strictly necessary. The line formats applied by the custom logger (CustomLogger) could have been applied by merely checking the logLevel in the Log method. The purpose of assigning the log format via configuration is that the developer can change the log format easily via app configuration, as the following example demonstrates.

In the wwwroot folder, add or update the appsettings.json file to include logging configuration. Set the log format to Long for all three log levels:

{
  "Logging": {
    "CustomLog": {
      "LogLevels": {
        "Information": "Long",
        "Warning": "Long",
        "Error": "Long"
      }
    }
  }
}

In the preceding example, notice that the entry for the custom logger configuration is CustomLog, which was applied to the custom logger provider (CustomLoggerProvider) as an alias with [ProviderAlias("CustomLog")]. The logging configuration could have been applied with the name CustomLoggerProvider instead of CustomLog, but use of the alias CustomLog is more user friendly.

In Program.cs consume the logging configuration. Add the following code:

builder.Logging.AddConfiguration(
    builder.Configuration.GetSection("Logging"));

The call to LoggingBuilderConfigurationExtensions.AddConfiguration can be placed either before or after adding the custom logger provider.

Run the app again. Select the Log Messages button. Notice that the logging configuration is applied from the appsettings.json file. All three log entries are in the long (LogFormat.Long) format:

[ 3: Information ] LoggingTest.Pages.Index - This is an information message.
[ 5: Warning ] LoggingTest.Pages.Index - This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

Log scopes

The Blazor WebAssembly developer tools console logger doesn't support log scopes. However, a custom logger can support log scopes. For an unsupported example that you can further develop to suit your needs, see the prototype in the dotnet/blazor-samples GitHub repository:

BlazorWebAssemblyScopesLogger sample app

The sample app uses standard ASP.NET Core BeginScope logging syntax to indicate scopes for logged messages. The Logger service in the following example is an ILogger<Index>, which is injected into the app's Index component (Pages/Index.razor).

using (Logger.BeginScope("L1"))
{
    Logger.LogInformation(3, "INFO: ONE scope.");
}

using (Logger.BeginScope("L1"))
{
    using (Logger.BeginScope("L2"))
    {
        Logger.LogInformation(3, "INFO: TWO scopes.");
    }
}

using (Logger.BeginScope("L1"))
{
    using (Logger.BeginScope("L2"))
    {
        using (Logger.BeginScope("L3"))
        {
            Logger.LogInformation(3, "INFO: THREE scopes.");
        }
    }
}

Output:

[ 3: Information ] ScopesLogger.Pages.Index - INFO: ONE scope. => L1 blazor.webassembly.js:1:35542
[ 3: Information ] ScopesLogger.Pages.Index - INFO: TWO scopes. => L1 => L2 blazor.webassembly.js:1:35542
[ 3: Information ] ScopesLogger.Pages.Index - INFO: THREE scopes. => L1 => L2 => L3

Hosted Blazor WebAssembly logging

A hosted Blazor WebAssembly app that prerenders its content executes component initialization code twice. Logging takes place server-side on the first execution of initialization code and client-side on the second execution of initialization code. Depending on the goal of logging during initialization, check logs server-side, client-side, or both.

SignalR client logging (Blazor Server)

On the client builder in Pages/_Layout.cshtml, pass in the configureSignalR configuration object that calls configureLogging with the log level.

For the configureLogging log level value, pass the argument as either the string or integer log level shown in the following table.

LogLevel String setting Integer setting
Trace trace 0
Debug debug 1
Information information 2
Warning warning 3
Error error 4
Critical critical 5
None none 6

Example 1: Set the Information log level with a string value:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.configureLogging("information");
    }
  });
</script>

Example 2: Set the Information log level with an integer value:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.configureLogging(2);
    }
  });
</script>

For more information on Blazor startup (Blazor.start), see ASP.NET Core Blazor startup.

SignalR client logging (Blazor WebAssembly)

In Blazor WebAssembly apps, set up app settings configuration as described in ASP.NET Core Blazor configuration. Place app settings files in wwwroot that contain a Logging:LogLevel:HubConnection app setting.

Note

As an alternative to using app settings, you can pass the LogLevel as the argument to LoggingBuilderExtensions.SetMinimumLevel when the hub connection is created in a Razor component. However, accidentally deploying the app to a production hosting environment with verbose logging may result in a performance penalty. We recommend using app settings to set the log level.

Provide a Logging:LogLevel:HubConnection app setting in the default appsettings.json file and in the Development environment app settings file. Use a typical less-verbose log level for the default, such as LogLevel.Warning. The default app settings value is what is used in Staging and Production environments if no app settings files for those environments are present. Use a verbose log level in the Development environment app settings file, such as LogLevel.Trace.

wwwroot/appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "HubConnection": "Warning"
    }
  }
}

wwwroot/appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "HubConnection": "Trace"
    }
  }
}

Important

Configuration in the preceding app settings files is only used by the app if the guidance in ASP.NET Core Blazor configuration is followed.

At the top of the Razor component file (.razor):

  • Inject an ILoggerProvider to add a WebAssemblyConsoleLogger to the logging providers passed to HubConnectionBuilder. Unlike a traditional ConsoleLogger, WebAssemblyConsoleLogger is a wrapper around browser-specific logging APIs (for example, console.log). Use of WebAssemblyConsoleLogger makes logging possible within Mono inside a browser context.
  • Inject an IConfiguration to read the Logging:LogLevel:HubConnection app setting.

Note

WebAssemblyConsoleLogger is internal and not supported for direct use in developer code.

@inject ILoggerProvider LoggerProvider
@inject IConfiguration Config

Note

The following example is based on the Index component in the SignalR with Blazor tutorial. Consult the tutorial for further details.

In the component's OnInitializedAsync method, use HubConnectionBuilderExtensions.ConfigureLogging to add the logging provider and set the minimum log level from configuration:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .ConfigureLogging(builder => 
        {
            builder.AddProvider(LoggerProvider);
            builder.SetMinimumLevel(
                Config.GetValue<LogLevel>("Logging:LogLevel:HubConnection"));
        })
        .Build();

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Note

In the preceding example, Navigation is an injected NavigationManager.

For more information on setting the app's environment for Blazor WebAssembly, see ASP.NET Core Blazor environments.

Additional resources

Configuration

Logging configuration can be loaded from app settings files. For more information, see ASP.NET Core Blazor configuration.

Razor component logging

Loggers respect app startup configuration. For configuration information, see ASP.NET Core Blazor configuration.

The using directive for Microsoft.Extensions.Logging is required to support IntelliSense completions for APIs, such as LogWarning and LogError.

The following example:

  • Injects an ILogger (ILogger<Counter>) object to create a logger. The log's category is the fully qualified name of the component's type, Counter.
  • Calls LogWarning to log at the Warning level.
  • Doesn't require additional setup in the app in order to log to the browser's developer tools console.

Pages/Counter1.razor:

@page "/counter-1"
@using Microsoft.Extensions.Logging
@inject ILogger<Counter> logger

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

The following example demonstrates logging with an ILoggerFactory in components.

Pages/Counter2.razor:

@page "/counter-2"
@using Microsoft.Extensions.Logging
@inject ILoggerFactory LoggerFactory

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        var logger = LoggerFactory.CreateLogger<Counter>();
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

Note

Guidance on popular browsers' developer tools can be found in the documentation of each browser maintainer:

For more information, see Logging in .NET Core and ASP.NET Core.

Logging in Blazor Server apps

For general ASP.NET Core logging guidance that pertains to Blazor Server, see Logging in .NET Core and ASP.NET Core.

Logging in Blazor WebAssembly apps

Not every feature of ASP.NET Core logging is supported in Blazor WebAssembly apps. For example, Blazor WebAssembly apps don't have access to the client's file system or network, so writing logs to the client's physical or network storage isn't possible. When using a third-party logging service designed to work with single-page apps (SPAs), follow the service's security guidance. Keep in mind that every piece of data, including keys or secrets stored in the Blazor WebAssembly app are insecure and can be easily discovered by malicious users.

Depending on the framework version and logging features, logging implementations may require adding the namespace for Microsoft.Extensions.Logging to Program.cs:

using Microsoft.Extensions.Logging;

Configure logging in Blazor WebAssembly apps with the WebAssemblyHostBuilder.Logging property. The Logging property is of type ILoggingBuilder, so the extension methods of ILoggingBuilder are supported.

To set the minimum logging level, call LoggingBuilderExtensions.SetMinimumLevel on the host builder in Program.cs with the LogLevel. The following example sets the minimum log level to Warning:

builder.Logging.SetMinimumLevel(LogLevel.Warning);

Custom logger provider in Blazor WebAssembly apps

The example in this section demonstrates a custom logger provider for further customization.

Add a package reference to the app for the Microsoft.Extensions.Logging.Configuration package.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Add the following custom logger configuration. The configuration establishes a LogLevels dictionary that sets a custom log format for three log levels: Information, Warning, and Error. A LogFormat enum is used to describe short (LogFormat.Short) and long (LogFormat.Long) formats.

CustomLoggerConfiguration.cs:

using Microsoft.Extensions.Logging;

public class CustomLoggerConfiguration
{
    public int EventId { get; set; }

    public Dictionary<LogLevel, LogFormat> LogLevels { get; set; } = 
        new()
        {
            [LogLevel.Information] = LogFormat.Short,
            [LogLevel.Warning] = LogFormat.Short,
            [LogLevel.Error] = LogFormat.Long
        };

    public enum LogFormat
    {
        Short,
        Long
    }
}

Add the following custom logger to the app. The CustomLogger outputs custom log formats based on the logLevel values defined in the preceding CustomLoggerConfiguration configuration.

using Microsoft.Extensions.Logging;
using static CustomLoggerConfiguration;

public sealed class CustomLogger : ILogger
{
    private readonly string name;
    private readonly Func<CustomLoggerConfiguration> getCurrentConfig;

    public CustomLogger(
        string name,
        Func<CustomLoggerConfiguration> getCurrentConfig) =>
        (this.name, this.getCurrentConfig) = (name, getCurrentConfig);

    public IDisposable BeginScope<TState>(TState state) => default!;

    public bool IsEnabled(LogLevel logLevel) =>
        getCurrentConfig().LogLevels.ContainsKey(logLevel);

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        CustomLoggerConfiguration config = getCurrentConfig();

        if (config.EventId == 0 || config.EventId == eventId.Id)
        {
            switch (config.LogLevels[logLevel])
            {
                case LogFormat.Short:
                    Console.WriteLine($"{name}: {formatter(state, exception)}");
                    break;
                case LogFormat.Long:
                    Console.WriteLine($"[{eventId.Id, 2}: {logLevel, -12}] {name} - {formatter(state, exception)}");
                    break;
                default:
                    // No-op
                    break;
            }
        }
    }
}

Add the following custom logger provider to the app. CustomLoggerProvider adopts an Options-based approach to configure the logger via built-in logging configuration features. For example, the app can set or change log formats via an appsettings.json file without requiring code changes to the custom logger, which is demonstrated at the end of this section.

CustomLoggerProvider.cs:

using System.Collections.Concurrent;
using Microsoft.Extensions.Options;

[ProviderAlias("CustomLog")]
public sealed class CustomLoggerProvider : ILoggerProvider
{
    private readonly IDisposable onChangeToken;
    private CustomLoggerConfiguration config;
    private readonly ConcurrentDictionary<string, CustomLogger> loggers =
        new(StringComparer.OrdinalIgnoreCase);

    public CustomLoggerProvider(
        IOptionsMonitor<CustomLoggerConfiguration> config)
    {
        this.config = config.CurrentValue;
        onChangeToken = config.OnChange(updatedConfig => this.config = updatedConfig);
    }

    public ILogger CreateLogger(string categoryName) =>
        loggers.GetOrAdd(categoryName, name => new CustomLogger(name, GetCurrentConfig));

    private CustomLoggerConfiguration GetCurrentConfig() => config;

    public void Dispose()
    {
        loggers.Clear();
        onChangeToken.Dispose();
    }
}

Add the following custom logger extensions.

CustomLoggerExtensions.cs:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;

public static class CustomLoggerExtensions
{
    public static ILoggingBuilder AddCustomLogger(
        this ILoggingBuilder builder)
    {
        builder.AddConfiguration();

        builder.Services.TryAddEnumerable(
            ServiceDescriptor.Singleton<ILoggerProvider, CustomLoggerProvider>());

        LoggerProviderOptions.RegisterProviderOptions
            <CustomLoggerConfiguration, CustomLoggerProvider>(builder.Services);

        return builder;
    }
}

In Program.cs on the host builder, clear the existing provider by calling ClearProviders and add the custom logging provider:

builder.Logging.ClearProviders().AddCustomLogger();

In the following Index component:

  • The debug message isn't logged.
  • The information message is logged in the short format (LogFormat.Short).
  • The warning message is logged in the short format (LogFormat.Short).
  • The error message is logged in the long format (LogFormat.Long).
  • The trace message isn't logged.

Pages/Index.razor:

@page "/"
@using Microsoft.Extensions.Logging
@inject ILogger<Index> Logger

<p>
    <button @onclick="LogMessages">Log Messages</button>
</p>

@code{
    private void LogMessages()
    {
        Logger.LogDebug(1, "This is a debug message.");
        Logger.LogInformation(3, "This is an information message.");
        Logger.LogWarning(5, "This is a warning message.");
        Logger.LogError(7, "This is an error message.");
        Logger.LogTrace(5!, "This is a trace message.");
    }
}

The following output is seen in the browser's developer tools console when the Log Messages button is selected. The log entries reflect the appropriate formats applied by the custom logger:

LoggingTest.Pages.Index: This is an information message.
LoggingTest.Pages.Index: This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

From a casual inspection of the preceding example, it's apparent that setting the log line formats via the dictionary in CustomLoggerConfiguration isn't strictly necessary. The line formats applied by the custom logger (CustomLogger) could have been applied by merely checking the logLevel in the Log method. The purpose of assigning the log format via configuration is that the developer can change the log format easily via app configuration, as the following example demonstrates.

In the wwwroot folder, add or update the appsettings.json file to include logging configuration. Set the log format to Long for all three log levels:

{
  "Logging": {
    "CustomLog": {
      "LogLevels": {
        "Information": "Long",
        "Warning": "Long",
        "Error": "Long"
      }
    }
  }
}

In the preceding example, notice that the entry for the custom logger configuration is CustomLog, which was applied to the custom logger provider (CustomLoggerProvider) as an alias with [ProviderAlias("CustomLog")]. The logging configuration could have been applied with the name CustomLoggerProvider instead of CustomLog, but use of the alias CustomLog is more user friendly.

In Program.cs consume the logging configuration. Add the following code:

builder.Logging.AddConfiguration(
    builder.Configuration.GetSection("Logging"));

The call to LoggingBuilderConfigurationExtensions.AddConfiguration can be placed either before or after adding the custom logger provider.

Run the app again. Select the Log Messages button. Notice that the logging configuration is applied from the appsettings.json file. All three log entries are in the long (LogFormat.Long) format:

[ 3: Information ] LoggingTest.Pages.Index - This is an information message.
[ 5: Warning ] LoggingTest.Pages.Index - This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

Hosted Blazor WebAssembly logging

A hosted Blazor WebAssembly app that prerenders its content executes component initialization code twice. Logging takes place server-side on the first execution of initialization code and client-side on the second execution of initialization code. Depending on the goal of logging during initialization, check logs server-side, client-side, or both.

SignalR client logging (Blazor Server)

On the client builder in Pages/_Layout.cshtml, pass in the configureSignalR configuration object that calls configureLogging with the log level.

For the configureLogging log level value, pass the argument as either the string or integer log level shown in the following table.

LogLevel String setting Integer setting
Trace trace 0
Debug debug 1
Information information 2
Warning warning 3
Error error 4
Critical critical 5
None none 6

Example 1: Set the Information log level with a string value:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.configureLogging("information");
    }
  });
</script>

Example 2: Set the Information log level with an integer value:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.configureLogging(2);
    }
  });
</script>

For more information on Blazor startup (Blazor.start), see ASP.NET Core Blazor startup.

SignalR client logging (Blazor WebAssembly)

In Blazor WebAssembly apps, set up app settings configuration as described in ASP.NET Core Blazor configuration. Place app settings files in wwwroot that contain a Logging:LogLevel:HubConnection app setting.

Note

As an alternative to using app settings, you can pass the LogLevel as the argument to LoggingBuilderExtensions.SetMinimumLevel when the hub connection is created in a Razor component. However, accidentally deploying the app to a production hosting environment with verbose logging may result in a performance penalty. We recommend using app settings to set the log level.

Provide a Logging:LogLevel:HubConnection app setting in the default appsettings.json file and in the Development environment app settings file. Use a typical less-verbose log level for the default, such as LogLevel.Warning. The default app settings value is what is used in Staging and Production environments if no app settings files for those environments are present. Use a verbose log level in the Development environment app settings file, such as LogLevel.Trace.

wwwroot/appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "HubConnection": "Warning"
    }
  }
}

wwwroot/appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "HubConnection": "Trace"
    }
  }
}

Important

Configuration in the preceding app settings files is only used by the app if the guidance in ASP.NET Core Blazor configuration is followed.

At the top of the Razor component file (.razor):

  • Add the namespace for Microsoft.Extensions.Logging.
  • Inject an ILoggerProvider to add a WebAssemblyConsoleLogger to the logging providers passed to HubConnectionBuilder. Unlike a traditional ConsoleLogger, WebAssemblyConsoleLogger is a wrapper around browser-specific logging APIs (for example, console.log). Use of WebAssemblyConsoleLogger makes logging possible within Mono inside a browser context.
  • Inject an IConfiguration to read the Logging:LogLevel:HubConnection app setting.

Note

WebAssemblyConsoleLogger is internal and not supported for direct use in developer code.

@using Microsoft.Extensions.Logging
@inject ILoggerProvider LoggerProvider
@inject IConfiguration Config

Note

The following example is based on the Index component in the SignalR with Blazor tutorial. Consult the tutorial for further details.

In the component's OnInitializedAsync method, use HubConnectionBuilderExtensions.ConfigureLogging to add the logging provider and set the minimum log level from configuration:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .ConfigureLogging(builder => 
        {
            builder.AddProvider(LoggerProvider);
            builder.SetMinimumLevel(
                Config.GetValue<LogLevel>("Logging:LogLevel:HubConnection"));
        })
        .Build();

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Note

In the preceding example, Navigation is an injected NavigationManager.

For more information on setting the app's environment for Blazor WebAssembly, see ASP.NET Core Blazor environments.

Additional resources

Configuration

Logging configuration can be loaded from app settings files. For more information, see ASP.NET Core Blazor configuration.

Razor component logging

Loggers respect app startup configuration. For configuration information, see ASP.NET Core Blazor configuration.

The using directive for Microsoft.Extensions.Logging is required to support IntelliSense completions for APIs, such as LogWarning and LogError.

The following example:

  • Injects an ILogger (ILogger<Counter>) object to create a logger. The log's category is the fully qualified name of the component's type, Counter.
  • Calls LogWarning to log at the Warning level.
  • Doesn't require additional setup in the app in order to log to the browser's developer tools console.

Pages/Counter1.razor:

@page "/counter-1"
@using Microsoft.Extensions.Logging
@inject ILogger<Counter> logger

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

The following example demonstrates logging with an ILoggerFactory in components.

Pages/Counter2.razor:

@page "/counter-2"
@using Microsoft.Extensions.Logging
@inject ILoggerFactory LoggerFactory

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        var logger = LoggerFactory.CreateLogger<Counter>();
        logger.LogWarning("Someone has clicked me!");

        currentCount++;
    }
}

Note

Guidance on popular browsers' developer tools can be found in the documentation of each browser maintainer:

For more information, see Logging in .NET Core and ASP.NET Core.

Logging in Blazor Server apps

For general ASP.NET Core logging guidance that pertains to Blazor Server, see Logging in .NET Core and ASP.NET Core.

Logging in Blazor WebAssembly apps

Not every feature of ASP.NET Core logging is supported in Blazor WebAssembly apps. For example, Blazor WebAssembly apps don't have access to the client's file system or network, so writing logs to the client's physical or network storage isn't possible. When using a third-party logging service designed to work with single-page apps (SPAs), follow the service's security guidance. Keep in mind that every piece of data, including keys or secrets stored in the Blazor WebAssembly app are insecure and can be easily discovered by malicious users.

Depending on the framework version and logging features, logging implementations may require adding the namespace for Microsoft.Extensions.Logging to Program.cs:

using Microsoft.Extensions.Logging;

Configure logging in Blazor WebAssembly apps with the WebAssemblyHostBuilder.Logging property. The Logging property is of type ILoggingBuilder, so the extension methods of ILoggingBuilder are supported.

To set the minimum logging level, call LoggingBuilderExtensions.SetMinimumLevel on the host builder in Program.cs with the LogLevel. The following example sets the minimum log level to Warning:

builder.Logging.SetMinimumLevel(LogLevel.Warning);

Custom logger provider in Blazor WebAssembly apps

The example in this section demonstrates a custom logger provider for further customization.

Add a package reference to the app for the Microsoft.Extensions.Logging.Configuration package.

Note

For guidance on adding packages to .NET apps, see the articles under Install and manage packages at Package consumption workflow (NuGet documentation). Confirm correct package versions at NuGet.org.

Add the following custom logger configuration. The configuration establishes a LogLevels dictionary that sets a custom log format for three log levels: Information, Warning, and Error. A LogFormat enum is used to describe short (LogFormat.Short) and long (LogFormat.Long) formats.

CustomLoggerConfiguration.cs:

using Microsoft.Extensions.Logging;

public class CustomLoggerConfiguration
{
    public int EventId { get; set; }

    public Dictionary<LogLevel, LogFormat> LogLevels { get; set; } = 
        new()
        {
            [LogLevel.Information] = LogFormat.Short,
            [LogLevel.Warning] = LogFormat.Short,
            [LogLevel.Error] = LogFormat.Long
        };

    public enum LogFormat
    {
        Short,
        Long
    }
}

Add the following custom logger to the app. The CustomLogger outputs custom log formats based on the logLevel values defined in the preceding CustomLoggerConfiguration configuration.

using Microsoft.Extensions.Logging;
using static CustomLoggerConfiguration;

public sealed class CustomLogger : ILogger
{
    private readonly string name;
    private readonly Func<CustomLoggerConfiguration> getCurrentConfig;

    public CustomLogger(
        string name,
        Func<CustomLoggerConfiguration> getCurrentConfig) =>
        (this.name, this.getCurrentConfig) = (name, getCurrentConfig);

    public IDisposable BeginScope<TState>(TState state) => default!;

    public bool IsEnabled(LogLevel logLevel) =>
        getCurrentConfig().LogLevels.ContainsKey(logLevel);

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }

        CustomLoggerConfiguration config = getCurrentConfig();

        if (config.EventId == 0 || config.EventId == eventId.Id)
        {
            switch (config.LogLevels[logLevel])
            {
                case LogFormat.Short:
                    Console.WriteLine($"{name}: {formatter(state, exception)}");
                    break;
                case LogFormat.Long:
                    Console.WriteLine($"[{eventId.Id, 2}: {logLevel, -12}] {name} - {formatter(state, exception)}");
                    break;
                default:
                    // No-op
                    break;
            }
        }
    }
}

Add the following custom logger provider to the app. CustomLoggerProvider adopts an Options-based approach to configure the logger via built-in logging configuration features. For example, the app can set or change log formats via an appsettings.json file without requiring code changes to the custom logger, which is demonstrated at the end of this section.

CustomLoggerProvider.cs:

using System.Collections.Concurrent;
using Microsoft.Extensions.Options;

[ProviderAlias("CustomLog")]
public sealed class CustomLoggerProvider : ILoggerProvider
{
    private readonly IDisposable onChangeToken;
    private CustomLoggerConfiguration config;
    private readonly ConcurrentDictionary<string, CustomLogger> loggers =
        new(StringComparer.OrdinalIgnoreCase);

    public CustomLoggerProvider(
        IOptionsMonitor<CustomLoggerConfiguration> config)
    {
        this.config = config.CurrentValue;
        onChangeToken = config.OnChange(updatedConfig => this.config = updatedConfig);
    }

    public ILogger CreateLogger(string categoryName) =>
        loggers.GetOrAdd(categoryName, name => new CustomLogger(name, GetCurrentConfig));

    private CustomLoggerConfiguration GetCurrentConfig() => config;

    public void Dispose()
    {
        loggers.Clear();
        onChangeToken.Dispose();
    }
}

Add the following custom logger extensions.

CustomLoggerExtensions.cs:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;

public static class CustomLoggerExtensions
{
    public static ILoggingBuilder AddCustomLogger(
        this ILoggingBuilder builder)
    {
        builder.AddConfiguration();

        builder.Services.TryAddEnumerable(
            ServiceDescriptor.Singleton<ILoggerProvider, CustomLoggerProvider>());

        LoggerProviderOptions.RegisterProviderOptions
            <CustomLoggerConfiguration, CustomLoggerProvider>(builder.Services);

        return builder;
    }
}

In Program.cs on the host builder, clear the existing provider by calling ClearProviders and add the custom logging provider:

builder.Logging.ClearProviders().AddCustomLogger();

In the following Index component:

  • The debug message isn't logged.
  • The information message is logged in the short format (LogFormat.Short).
  • The warning message is logged in the short format (LogFormat.Short).
  • The error message is logged in the long format (LogFormat.Long).
  • The trace message isn't logged.

Pages/Index.razor:

@page "/"
@using Microsoft.Extensions.Logging
@inject ILogger<Index> Logger

<p>
    <button @onclick="LogMessages">Log Messages</button>
</p>

@code{
    private void LogMessages()
    {
        Logger.LogDebug(1, "This is a debug message.");
        Logger.LogInformation(3, "This is an information message.");
        Logger.LogWarning(5, "This is a warning message.");
        Logger.LogError(7, "This is an error message.");
        Logger.LogTrace(5!, "This is a trace message.");
    }
}

The following output is seen in the browser's developer tools console when the Log Messages button is selected. The log entries reflect the appropriate formats applied by the custom logger:

LoggingTest.Pages.Index: This is an information message.
LoggingTest.Pages.Index: This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

From a casual inspection of the preceding example, it's apparent that setting the log line formats via the dictionary in CustomLoggerConfiguration isn't strictly necessary. The line formats applied by the custom logger (CustomLogger) could have been applied by merely checking the logLevel in the Log method. The purpose of assigning the log format via configuration is that the developer can change the log format easily via app configuration, as the following example demonstrates.

In the wwwroot folder, add or update the appsettings.json file to include logging configuration. Set the log format to Long for all three log levels:

{
  "Logging": {
    "CustomLog": {
      "LogLevels": {
        "Information": "Long",
        "Warning": "Long",
        "Error": "Long"
      }
    }
  }
}

In the preceding example, notice that the entry for the custom logger configuration is CustomLog, which was applied to the custom logger provider (CustomLoggerProvider) as an alias with [ProviderAlias("CustomLog")]. The logging configuration could have been applied with the name CustomLoggerProvider instead of CustomLog, but use of the alias CustomLog is more user friendly.

In Program.cs consume the logging configuration. Add the following code:

builder.Logging.AddConfiguration(
    builder.Configuration.GetSection("Logging"));

The call to LoggingBuilderConfigurationExtensions.AddConfiguration can be placed either before or after adding the custom logger provider.

Run the app again. Select the Log Messages button. Notice that the logging configuration is applied from the appsettings.json file. All three log entries are in the long (LogFormat.Long) format:

[ 3: Information ] LoggingTest.Pages.Index - This is an information message.
[ 5: Warning ] LoggingTest.Pages.Index - This is a warning message.
[ 7: Error ] LoggingTest.Pages.Index - This is an error message.

Hosted Blazor WebAssembly logging

A hosted Blazor WebAssembly app that prerenders its content executes component initialization code twice. Logging takes place server-side on the first execution of initialization code and client-side on the second execution of initialization code. Depending on the goal of logging during initialization, check logs server-side, client-side, or both.

SignalR client logging (Blazor Server)

On the client builder in Pages/_Layout.cshtml, pass in the configureSignalR configuration object that calls configureLogging with the log level.

For the configureLogging log level value, pass the argument as either the string or integer log level shown in the following table.

LogLevel String setting Integer setting
Trace trace 0
Debug debug 1
Information information 2
Warning warning 3
Error error 4
Critical critical 5
None none 6

Example 1: Set the Information log level with a string value:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.configureLogging("information");
    }
  });
</script>

Example 2: Set the Information log level with an integer value:

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.configureLogging(2);
    }
  });
</script>

For more information on Blazor startup (Blazor.start), see ASP.NET Core Blazor startup.

SignalR client logging (Blazor WebAssembly)

In Blazor WebAssembly apps, set up app settings configuration as described in ASP.NET Core Blazor configuration. Place app settings files in wwwroot that contain a Logging:LogLevel:HubConnection app setting.

Note

As an alternative to using app settings, you can pass the LogLevel as the argument to LoggingBuilderExtensions.SetMinimumLevel when the hub connection is created in a Razor component. However, accidentally deploying the app to a production hosting environment with verbose logging may result in a performance penalty. We recommend using app settings to set the log level.

Provide a Logging:LogLevel:HubConnection app setting in the default appsettings.json file and in the Development environment app settings file. Use a typical less-verbose log level for the default, such as LogLevel.Warning. The default app settings value is what is used in Staging and Production environments if no app settings files for those environments are present. Use a verbose log level in the Development environment app settings file, such as LogLevel.Trace.

wwwroot/appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "HubConnection": "Warning"
    }
  }
}

wwwroot/appsettings.Development.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "HubConnection": "Trace"
    }
  }
}

Important

Configuration in the preceding app settings files is only used by the app if the guidance in ASP.NET Core Blazor configuration is followed.

At the top of the Razor component file (.razor):

  • Add the namespace for Microsoft.Extensions.Logging.
  • Inject an ILoggerProvider to add a WebAssemblyConsoleLogger to the logging providers passed to HubConnectionBuilder. Unlike a traditional ConsoleLogger, WebAssemblyConsoleLogger is a wrapper around browser-specific logging APIs (for example, console.log). Use of WebAssemblyConsoleLogger makes logging possible within Mono inside a browser context.
  • Inject an IConfiguration to read the Logging:LogLevel:HubConnection app setting.

Note

WebAssemblyConsoleLogger is internal and not supported for direct use in developer code.

@using Microsoft.Extensions.Logging
@inject ILoggerProvider LoggerProvider
@inject IConfiguration Config

Note

The following example is based on the Index component in the SignalR with Blazor tutorial. Consult the tutorial for further details.

In the component's OnInitializedAsync method, use HubConnectionBuilderExtensions.ConfigureLogging to add the logging provider and set the minimum log level from configuration:

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .ConfigureLogging(builder => 
        {
            builder.AddProvider(LoggerProvider);
            builder.SetMinimumLevel(
                Config.GetValue<LogLevel>("Logging:LogLevel:HubConnection"));
        })
        .Build();

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

Note

In the preceding example, Navigation is an injected NavigationManager.

For more information on setting the app's environment for Blazor WebAssembly, see ASP.NET Core Blazor environments.

Additional resources