Logging in .NET

.NET supports a logging API that works with a variety of built-in and third-party logging providers. This article shows how to use the logging API with built-in providers. Most of the code examples shown in this article apply to any .NET app that uses the Generic Host. For apps that don't use the Generic Host, see Non-host console app.

Create logs

To create logs, use an ILogger<TCategoryName> object from dependency injection (DI).

The following example:

  • Creates a logger, ILogger<Worker>, which uses a log category of the fully qualified name of the type Worker. The log category is a string that is associated with each log.
  • Calls LogInformation to log at the Information level. The Log level indicates the severity of the logged event.
public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger) =>
        _logger = logger;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken);
        }
    }
}

Levels and categories are explained in more detail later in this article.

Configure logging

Logging configuration is commonly provided by the Logging section of appsettings.{Environment}.json files. The following appsettings.Development.json file is generated by the .NET Worker service templates:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

In the preceding JSON:

  • The "Default", "Microsoft", and "Microsoft.Hosting.Lifetime" categories are specified.
  • The "Microsoft" category applies to all categories that start with "Microsoft".
  • The "Microsoft" category logs at log level Warning and higher.
  • The "Microsoft.Hosting.Lifetime" category is more specific than the "Microsoft" category, so the "Microsoft.Hosting.Lifetime" category logs at log level "Information" and higher.
  • A specific log provider is not specified, so LogLevel applies to all the enabled logging providers except for the Windows EventLog.

The Logging property can have LogLevel and log provider properties. The LogLevel specifies the minimum level to log for selected categories. In the preceding JSON, Information and Warning log levels are specified. LogLevel indicates the severity of the log and ranges from 0 to 6:

Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5, and None = 6.

When a LogLevel is specified, logging is enabled for messages at the specified level and higher. In the preceding JSON, the Default category is logged for Information and higher. For example, Information, Warning, Error, and Critical messages are logged. If no LogLevel is specified, logging defaults to the Information level. For more information, see Log levels.

A provider property can specify a LogLevel property. LogLevel under a provider specifies levels to log for that provider, and overrides the non-provider log settings. Consider the following appsettings.json file:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information",
                "Microsoft.Hosting": "Trace"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Default": "Warning"
            }
        }
    }
}

Settings in Logging.{ProviderName}.LogLevel override settings in Logging.LogLevel. In the preceding JSON, the Debug provider's default log level is set to Information:

Logging:Debug:LogLevel:Default:Information

The preceding setting specifies the Information log level for every Logging:Debug: category except Microsoft.Hosting. When a specific category is listed, the specific category overrides the default category. In the preceding JSON, the Logging:Debug:LogLevel categories "Microsoft.Hosting" and "Default" override the settings in Logging:LogLevel

The minimum log level can be specified for any of:

  • Specific providers: For example, Logging:EventSource:LogLevel:Default:Information
  • Specific categories: For example, Logging:LogLevel:Microsoft:Warning
  • All providers and all categories: Logging:LogLevel:Default:Warning

Any logs below the minimum level are not:

  • Passed to the provider.
  • Logged or displayed.

To suppress all logs, specify LogLevel.None. LogLevel.None has a value of 6, which is higher than LogLevel.Critical (5).

If a provider supports log scopes, IncludeScopes indicates whether they're enabled. For more information, see log scopes

The following appsettings.json file contains settings for all of the built-in providers:

{
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Warning"
        },
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft.Extensions.Hosting": "Warning",
                "Default": "Information"
            }
        },
        "EventSource": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "EventLog": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "AzureAppServicesFile": {
            "IncludeScopes": true,
            "LogLevel": {
                "Default": "Warning"
            }
        },
        "AzureAppServicesBlob": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Information"
            }
        },
        "ApplicationInsights": {
            "LogLevel": {
                "Default": "Information"
            }
        }
    }
}

In the preceding sample:

  • The categories and levels are not suggested values. The sample is provided to show all the default providers.
  • Settings in Logging.{ProviderName}.LogLevel override settings in Logging.LogLevel. For example, the level in Debug.LogLevel.Default overrides the level in LogLevel.Default.
  • Each provider's alias is used. Each provider defines an alias that can be used in configuration in place of the fully qualified type name. The built-in providers' aliases are:
    • Console
    • Debug
    • EventSource
    • EventLog
    • AzureAppServicesFile
    • AzureAppServicesBlob
    • ApplicationInsights

Set log level by command line, environment variables, and other configuration

Log level can be set by any of the configuration providers.

The following commands:

  • Set the environment key Logging:LogLevel:Microsoft to a value of Information on Windows.
  • Test the settings when using an app created with the .NET Worker service templates. The dotnet run command must be run in the project directory after using set.
set Logging__LogLevel__Microsoft=Information
dotnet run

The preceding environment setting:

  • Is only set in processes launched from the command window they were set in.
  • Isn't read by apps launched with Visual Studio.

The following setx command also sets the environment key and value on Windows. Unlike set, setx settings are persisted. The /M switch sets the variable in the system environment. If /M isn't used, a user environment variable is set.

setx Logging__LogLevel__Microsoft=Information /M

On Azure App Service, select New application setting on the Settings > Configuration page. Azure App Service application settings are:

  • Encrypted at rest and transmitted over an encrypted channel.
  • Exposed as environment variables.

For more information on setting .NET configuration values using environment variables, see environment variables.

How filtering rules are applied

When an ILogger<TCategoryName> object is created, the ILoggerFactory object selects a single rule per provider to apply to that logger. All messages written by an ILogger instance are filtered based on the selected rules. The most specific rule for each provider and category pair is selected from the available rules.

The following algorithm is used for each provider when an ILogger is created for a given category:

  • Select all rules that match the provider or its alias. If no match is found, select all rules with an empty provider.
  • From the result of the preceding step, select rules with longest matching category prefix. If no match is found, select all rules that don't specify a category.
  • If multiple rules are selected, take the last one.
  • If no rules are selected, use LoggingBuilderExtensions.SetMinimumLevel(ILoggingBuilder, LogLevel) to specify the minimum logging level.

Log category

When an ILogger object is created, a category is specified. That category is included with each log message created by that instance of ILogger. The category string is arbitrary, but the convention is to use the class name. For example, in an application with a service define like the following object, the category might be "Example.DefaultService":

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger<DefaultService> _logger;

        public DefaultService(ILogger<DefaultService> logger) =>
            _logger = logger;

        // ...
    }
}

To explicitly specify the category, call LoggerFactory.CreateLogger:

namespace Example
{
    public class DefaultService : IService
    {
        private readonly ILogger _logger;

        public DefaultService(ILoggerFactory loggerFactory) =>
            _logger = loggerFactory.CreateLogger("CustomCategory");

        // ...
    }
}

Calling CreateLogger with a fixed name can be useful when used in multiple methods so the events can be organized by category.

ILogger<T> is equivalent to calling CreateLogger with the fully qualified type name of T.

Log level

The following table lists the LogLevel values, the convenience Log{LogLevel} extension method, and the suggested usage:

LogLevel Value Method Description
Trace 0 LogTrace Contain the most detailed messages. These messages may contain sensitive app data. These messages are disabled by default and should not be enabled in production.
Debug 1 LogDebug For debugging and development. Use with caution in production due to the high volume.
Information 2 LogInformation Tracks the general flow of the app. May have long-term value.
Warning 3 LogWarning For abnormal or unexpected events. Typically includes errors or conditions that don't cause the app to fail.
Error 4 LogError For errors and exceptions that cannot be handled. These messages indicate a failure in the current operation or request, not an app-wide failure.
Critical 5 LogCritical For failures that require immediate attention. Examples: data loss scenarios, out of disk space.
None 6 Specifies that no messages should be written.

In the previous table, the LogLevel is listed from lowest to highest severity.

The Log method's first parameter, LogLevel, indicates the severity of the log. Rather than calling Log(LogLevel, ...), most developers call the Log{LogLevel} extension methods. The Log{LogLevel} extension methods call the Log method and specify the LogLevel. For example, the following two logging calls are functionally equivalent and produce the same log:

public void LogDetails()
{
    var logMessage = "Details for log.";

    _logger.Log(LogLevel.Information, AppLogEvents.Details, logMessage);
    _logger.LogInformation(AppLogEvents.Details, logMessage);
}

AppLogEvents.Details is the event ID, and is implicitly represented by a constant Int32 value. AppLogEvents is a class that exposes various named identifier constants and is displayed in the Log event ID section.

The following code creates Information and Warning logs:

public async Task<T> GetAsync<T>(string id)
{
    _logger.LogInformation(AppLogEvents.Read, "Reading value for {Id}", id);

    var result = await _repository.GetAsync(id);
    if (result is null)
    {
        _logger.LogWarning(AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
    }

    return result;
}

In the preceding code, the first Log{LogLevel} parameter,AppLogEvents.Read, is the Log event ID. The second parameter is a message template with placeholders for argument values provided by the remaining method parameters. The method parameters are explained in the message template section later in this article.

Configure the appropriate log level and call the correct Log{LogLevel} methods to control how much log output is written to a particular storage medium. For example:

  • In production:
    • Logging at the Trace or Information levels produces a high-volume of detailed log messages. To control costs and not exceed data storage limits, log Trace and Information level messages to a high-volume, low-cost data store. Consider limiting Trace and Information to specific categories.
    • Logging at Warning through Critical levels should produce few log messages.
      • Costs and storage limits usually aren't a concern.
      • Few logs allow more flexibility in data store choices.
  • In development:
    • Set to Warning.
    • Add Trace or Information messages when troubleshooting. To limit output, set Trace or Information only for the categories under investigation.

The following JSON sets Logging:Console:LogLevel:Microsoft:Information:

{
    "Logging": {
        "LogLevel": {
            "Microsoft": "Warning"
        },
        "Console": {
            "LogLevel": {
                "Microsoft": "Information"
            }
        }
    }
}

Log event ID

Each log can specify an event identifer, the EventId is a structure with an Id and optional Name readonly properties. The sample source code uses the AppLogEvents class to define event IDs:

internal static class AppLogEvents
{
    internal const int Create = 1000;
    internal const int Read = 1001;
    internal const int Update = 1002;
    internal const int Delete = 1003;

    internal const int Details = 3000;
    internal const int Error = 3001;

    internal const int ReadNotFound = 4000;
    internal const int UpdateNotFound = 4001;

    // ...
}

An event ID associates a set of events. For example, all logs related to reading values from a repository might be 1001.

The logging provider may log the event ID in an ID field, in the logging message, or not at all. The Debug provider doesn't show event IDs. The console provider shows event IDs in brackets after the category:

info: Example.DefaultService.GetAsync[1001]
      Reading value for a1b2c3
warn: Example.DefaultService.GetAsync[4000]
      GetAsync(a1b2c3) not found

Some logging providers store the event ID in a field, which allows for filtering on the ID.

Log message template

Each log API uses a message template. The message template can contain placeholders for which arguments are provided. Use names for the placeholders, not numbers. The order of placeholders, not their names, determines which parameters are used to provide their values. In the following code, the parameter names are out of sequence in the message template:

string p1 = "param1";
string p2 = "param2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

The preceding code creates a log message with the parameter values in sequence:

Parameter values: param1, param2

This approach allows logging providers to implement semantic or structured logging. The arguments themselves are passed to the logging system, not just the formatted message template. This enables logging providers to store the parameter values as fields. Consider the following logger method:

_logger.LogInformation("Getting item {Id} at {RunTime}", id, DateTime.Now);

For example, when logging to Azure Table Storage:

  • Each Azure Table entity can have ID and RunTime properties.
  • Tables with properties simplify queries on logged data. For example, a query can find all logs within a particular RunTime range without having to parse the time out of the text message.

Log exceptions

The logger methods have overloads that take an exception parameter:

public void Test(string id)
{
    try
    {
        if (id == "none")
        {
            throw new Exception("Default Id detected.");
        }
    }
    catch (Exception ex)
    {
        _logger.LogWarning(
            AppLogEvents.Error, ex,
            "Failed to process iteration: {Id}", id)
    }
}

Exception logging is provider-specific.

Default log level

If the default log level is not set, the default log level value is Information.

For example, consider the following worker service app:

  • Created with the .NET Worker templates.
  • appsettings.json and appsettings.Development.json deleted or renamed.

With the preceding setup, navigating to the privacy or home page produces many Trace, Debug, and Information messages with Microsoft in the category name.

The following code sets the default log level when the default log level is not set in configuration:

class Program
{
    static Task Main(string[] args) =>
        CreateHostBuilder(args).Build().RunAsync();

    static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning));
}

Filter function

A filter function is invoked for all providers and categories that don't have rules assigned to them by configuration or code:

class Program
{
    static Task Main(string[] args) =>
        CreateHostBuilder(args).Build().RunAsync();

    static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(logging =>
                logging.AddFilter((provider, category, logLevel) =>
                {
                    return provider.Contains("ConsoleLoggerProvider")
                        && (category.Contains("Example") || category.Contains("Microsoft"))
                        && logLevel >= LogLevel.Information;
                }));
}

The preceding code displays console logs when the category contains Example or Microsoft and the log level is Information or higher.

Log scopes

A scope can group a set of logical operations. This grouping can be used to attach the same data to each log that's created as part of a set. For example, every log created as part of processing a transaction can include the transaction ID.

A scope:

The following providers support scopes:

Use a scope by wrapping logger calls in a using block:

public async Task<T> GetAsync<T>(string id)
{
    T result;

    using (_logger.BeginScope("using block message"))
    {
        _logger.LogInformation(
            AppLogEvents.Read, "Reading value for {Id}", id);

        var result = await _repository.GetAsync(id);
        if (result is null)
        {
            _logger.LogWarning(
                AppLogEvents.ReadNotFound, "GetAsync({Id}) not found", id);
        }
    }

    return result;
}

The following JSON enables scopes for the console provider:

{
    "Logging": {
        "Debug": {
            "LogLevel": {
                "Default": "Information"
            }
        },
        "Console": {
            "IncludeScopes": true,
            "LogLevel": {
                "Microsoft": "Warning",
                "Default": "Information"
            }
        },
        "LogLevel": {
            "Default": "Debug"
        }
    }
}

The following code enables scopes for the console provider:

class Program
{
    static Task Main(string[] args) =>
        CreateHostBuilder(args).Build().RunAsync();

    static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging((_, logging) =>
                logging.ClearProviders()
                    .AddConsole(options => options.IncludeScopes = true));
}

Non-host console app

Logging code for apps without a Generic Host differs in the way providers are added and loggers are created. In a non-host console app, call the provider's Add{provider name} extension method while creating a LoggerFactory:

class Program
{
    static void Main(string[] args)
    {
        using var loggerFactory = LoggerFactory.Create(builder =>
        {
            builder
                .AddFilter("Microsoft", LogLevel.Warning)
                .AddFilter("System", LogLevel.Warning)
                .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
                .AddConsole();
        });

        ILogger logger = loggerFactory.CreateLogger<Program>();
        logger.LogInformation("Example log message");
    }
}

The loggerFactory object is used to create an ILogger instance.

Create logs in Main

The following code logs in Main by getting an ILogger instance from DI after building the host:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

class Program
{
    static Task Main(string[] args)
    {
        IHost host = Host.CreateDefaultBuilder(args).Build();

        var logger = host.Services.GetRequiredService<ILogger<Program>>();
        logger.LogInformation("Host created.");

        return host.RunAsync();
    }
}

No asynchronous logger methods

Logging should be so fast that it isn't worth the performance cost of asynchronous code. If a logging data store is slow, don't write to it directly. Consider writing the log messages to a fast store initially, then moving them to the slow store later. For example, when logging to SQL Server, don't do so directly in a Log method, since the Log methods are synchronous. Instead, synchronously add log messages to an in-memory queue and have a background worker pull the messages out of the queue to do the asynchronous work of pushing data to SQL Server.

Change log levels in a running app

The Logging API doesn't include a scenario to change log levels while an app is running. However, some configuration providers are capable of reloading configuration, which takes immediate effect on logging configuration. For example, the File Configuration Provider reloads logging configuration by default. If configuration is changed in code while an app is running, the app can call IConfigurationRoot.Reload to update the app's logging configuration.

NuGet packages

The ILogger<TCategoryName> and ILoggerFactory interfaces and implementations are included in the .NET Core SDK. They are also available in the following NuGet packages:

Apply log filter rules in code

The preferred approach for setting log filter rules is by using Configuration.

The following example shows how to register filter rules in code:

class Program
{
    static Task Main(string[] args) =>
        CreateHostBuilder(args).Build().RunAsync();

    static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureLogging(logging =>
               logging.AddFilter("System", LogLevel.Debug)
                  .AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information)
                  .AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Trace));
}

logging.AddFilter("System", LogLevel.Debug) specifies the System category and log level Debug. The filter is applied to all providers because a specific provider was not configured.

AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information) specifies:

  • The Debug logging provider.
  • Log level Information and higher.
  • All categories starting with "Microsoft".

See also