This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Note
Access to this page requires authorization. You can trysigning in orchanging directories.
Access to this page requires authorization. You can trychanging directories.
.NET supports high performance, structured logging via theILogger API to help monitor application behavior and diagnose issues. Configure differentlogging providers to write logs to different destinations. Basic logging providers are built-in, and many third-party providers are available.
This first example shows the basics, but it's only suitable for a trivial console app. This sample console app relies on the following NuGet packages:
In the next section you see how to improve the code considering scale, performance, configuration, and typical programming patterns.
using Microsoft.Extensions.Logging;using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole());ILogger logger = factory.CreateLogger("Program");logger.LogInformation("Hello World! Logging is {Description}.", "fun");The preceding example:
ILoggerFactory stores all the configuration that determines where log messages are sent. In this case, configure the consolelogging provider so that log messages are written to the console.string that's associated with each message logged by theILogger object. It groups log messages from the same class (or category) together when searching or filtering logs.Information level. Thelog level indicates the severity of the logged event and filters out less important log messages. The log entry also includes amessage template"Hello World! Logging is {Description}." and a key-value pairDescription = fun. The key name (or placeholder) comes from the word inside the curly braces in the template, and the value comes from the remaining method argument.This project file for this example includes two NuGet packages:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" /> </ItemGroup></Project>Tip
All of the logging example source code is available in theSamples Browser for download. For more information, seeBrowse code samples: Logging in .NET.
Consider making these changes to the previous example when logging in a less trivial scenario:
If your application usesDependency Injection (DI) or a host such as ASP.NET'sWebApplication orGeneric Host, useILoggerFactory andILogger objects from their respective DI containers rather than creating them directly. For more information, seeIntegration with DI and Hosts.
Loggingcompile-time source generation is usually a better alternative toILogger extension methods likeLogInformation. Logging source generation offers better performance, stronger typing, and avoids spreadingstring constants throughout your methods. The tradeoff is that using this technique requires a bit more code.
using Microsoft.Extensions.Logging;internal partial class Program{ static void Main(string[] args) { using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole()); ILogger logger = factory.CreateLogger("Program"); LogStartupMessage(logger, "fun"); } [LoggerMessage(Level = LogLevel.Information, Message = "Hello World! Logging is {Description}.")] static partial void LogStartupMessage(ILogger logger, string description);}Type to make this naming easy to do.using Microsoft.Extensions.Logging;internal class Program{ static void Main(string[] args) { using ILoggerFactory factory = LoggerFactory.Create(builder => builder.AddConsole()); ILogger logger = factory.CreateLogger<Program>(); logger.LogInformation("Hello World! Logging is {Description}.", "fun"); }}using Microsoft.Extensions.Logging;using OpenTelemetry.Logs;using ILoggerFactory factory = LoggerFactory.Create(builder =>{ builder.AddOpenTelemetry(logging => { logging.AddOtlpExporter(); });});ILogger logger = factory.CreateLogger("Program");logger.LogInformation("Hello World! Logging is {Description}.", "fun");If your application usesDependency Injection (DI) or a host such as ASP.NET'sWebApplication orGeneric Host, useILoggerFactory andILogger objects from the DI container rather than creating them directly.
This example gets an ILogger object in a hosted app usingASP.NET Minimal APIs:
var builder = WebApplication.CreateBuilder(args);builder.Services.AddSingleton<ExampleHandler>();var app = builder.Build();var handler = app.Services.GetRequiredService<ExampleHandler>();app.MapGet("/", handler.HandleRequest);app.Run();partial class ExampleHandler(ILogger<ExampleHandler> logger){ public string HandleRequest() { LogHandleRequest(logger); return "Hello World"; } [LoggerMessage(LogLevel.Information, "ExampleHandler.HandleRequest was called")] public static partial void LogHandleRequest(ILogger logger);}The preceding example:
ExampleHandler and mapped incoming web requests to run theExampleHandler.HandleRequest function.ILogger<ExampleHandler>.ILogger<TCategoryName> derives fromILogger and indicates which category theILogger object has. The DI container locates anILogger with the correct category and supplies it as the constructor argument. If noILogger with that category exists yet, the DI container automatically creates it from theILoggerFactory in the service provider.logger parameter received in the constructor is used for logging in theHandleRequest function.Host builders initializedefault configuration, then add a configuredILoggerFactory object to the host's DI container when the host is built. Before the host is built, adjust the logging configuration viaHostApplicationBuilder.Logging,WebApplicationBuilder.Logging, or similar APIs on other hosts. Hosts also apply logging configuration from default configuration sources likeappsettings.json and environment variables. For more information, seeConfiguration in .NET.
This example expands on the previous one to customize theILoggerFactory provided byWebApplicationBuilder. It addsOpenTelemetry as a logging provider transmitting the logs overOTLP (OpenTelemetry protocol):
var builder = WebApplication.CreateBuilder(args);builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter());builder.Services.AddSingleton<ExampleHandler>();var app = builder.Build();If you're using a DI container without a host, useAddLogging to configure and addILoggerFactory to the container.
using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;// Add services to the container including loggingvar services = new ServiceCollection();services.AddLogging(builder => builder.AddConsole());services.AddSingleton<ExampleService>();IServiceProvider serviceProvider = services.BuildServiceProvider();// Get the ExampleService object from the containerExampleService service = serviceProvider.GetRequiredService<ExampleService>();// Do some pretend workservice.DoSomeWork(10, 20);class ExampleService(ILogger<ExampleService> logger){ public void DoSomeWork(int x, int y) { logger.LogInformation("DoSomeWork was called. x={X}, y={Y}", x, y); }}The preceding example:
ILoggerFactory configured to write to the consoleExampleService to the containerExampleService from the DI container, which also automatically created anILogger<ExampleService> to use as the constructor argument.ExampleService.DoSomeWork, which used theILogger<ExampleService> to log a message to the console.Set logging configuration in code or via external sources, such as config files and environment variables. Using external configuration is beneficial when possible because you can change it without rebuilding the application. However, some tasks, such as setting logging providers, can only be configured from code.
For apps thatuse a host, the"Logging" section ofappsettings.{Environment}.json files commonly provides logging configuration. For apps that don't use a host,set up external configuration sources explicitly orconfigure them in code instead.
The followingappsettings.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:
"Default","Microsoft", and"Microsoft.Hosting.Lifetime" log level categories are specified."Default" value applies to all categories that aren't otherwise specified, effectively making all default values for all categories"Information". Override this behavior by specifying a value for a category."Microsoft" category applies to all categories that start with"Microsoft"."Microsoft" category logs at a log level ofWarning and higher."Microsoft.Hosting.Lifetime" category is more specific than the"Microsoft" category, so the"Microsoft.Hosting.Lifetime" category logs at log level"Information" and higher.LogLevel applies to all the enabled logging providers except for theWindows EventLog.TheLogging property can haveLogLevel and log provider properties. TheLogLevel specifies the minimumlevel to log for selected categories. In the preceding JSON,Information andWarning 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, andNone = 6.
When aLogLevel is specified, logging is enabled for messages at the specified level and higher. In the preceding JSON, theDefault category is logged forInformation and higher. For example,Information,Warning,Error, andCritical messages are logged. If noLogLevel is specified, logging defaults to theInformation level. For more information, seeLog levels.
A provider property can specify aLogLevel property.LogLevel under a provider specifies levels to log for that provider, and overrides the non-provider log settings. Consider the followingappsettings.json file:
{ "Logging": { "LogLevel": { "Default": "Error", "Microsoft": "Warning" }, "Debug": { "LogLevel": { "Default": "Information", "Microsoft.Hosting": "Trace" } }, "EventSource": { "LogLevel": { "Default": "Warning" } } }}Settings inLogging.{ProviderName}.LogLevel override settings inLogging.LogLevel. In the preceding JSON, theDebug provider's default log level is set toInformation:
Logging:Debug:LogLevel:Default:Information
The preceding setting specifies theInformation log level for everyLogging:Debug: category exceptMicrosoft.Hosting. When a specific category is listed, the specific category overrides the default category. In the preceding JSON, theLogging:Debug:LogLevel categories"Microsoft.Hosting" and"Default" override the settings inLogging:LogLevel.
Specify the minimum log level for any of these:
Logging:EventSource:LogLevel:Default:InformationLogging:LogLevel:Microsoft:WarningLogging:LogLevel:Default:WarningAny logs below the minimum levelaren't:
To suppress all logs, specifyLogLevel.None.LogLevel.None has a value of 6, which is higher thanLogLevel.Critical (5).
If a provider supportslog scopes,IncludeScopes indicates whether they're enabled. For more information, seelog scopes.
The followingappsettings.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:
Logging.{ProviderName}.LogLevel override settings inLogging.LogLevel. For example, the level inDebug.LogLevel.Default overrides the level inLogLevel.Default.ConsoleDebugEventSourceEventLogAzureAppServicesFileAzureAppServicesBlobApplicationInsightsSet the log level using any of theconfiguration providers. For example, create a persisted environment variable namedLogging:LogLevel:Microsoft with a value ofInformation.
Create and assign persisted environment variable, given the log level value.
:: Assigns the env var to the valuesetx "Logging__LogLevel__Microsoft" "Information" /MIn anew instance of theCommand Prompt, read the environment variable.
:: Prints the env var valueecho %Logging__LogLevel__Microsoft%The preceding environment setting persists in the environment. To test the settings when using an app created with the .NET Worker service templates, use thedotnet run command in the project directory after the environment variable is assigned.
dotnet runTip
After setting an environment variable, restart your integrated development environment (IDE) to ensure that newly added environment variables are available.
OnAzure App Service, selectNew application setting on theSettings > Configuration page. Azure App Service application settings are:
For more information on setting .NET configuration values using environment variables, seeenvironment variables.
To configure logging in code, use theILoggingBuilder API. You can access it from different places:
ILoggerFactory directly, configure inLoggerFactory.Create.This example shows setting the consolelogging provider and severalfilters.
using Microsoft.Extensions.Logging;using var loggerFactory = LoggerFactory.Create(static builder =>{ builder .AddFilter("Microsoft", LogLevel.Warning) .AddFilter("System", LogLevel.Warning) .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug) .AddConsole();});ILogger logger = loggerFactory.CreateLogger<Program>();logger.LogDebug("Hello {Target}", "Everyone");In the preceding example,AddFilteradjusts the log level that's enabled for various categories.AddConsole adds the console logging provider. By default, logs withDebug severity aren't enabled, but because the configuration adjusted the filters, the debug message "Hello Everyone" is displayed on the console.
When anILogger<TCategoryName> object is created, theILoggerFactory object selects a single rule per provider to apply to that logger. TheILogger instance filters all messages it writes 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 anILogger is created for a given category:
When anILogger object is created, acategory is specified. That category is included with each log message created by that instance ofILogger. The category string is arbitrary, but the convention is to use the fully qualified class name. For example, in an application with a service defined 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; // ... }}If further categorization is desired, the convention is to use a hierarchical name by appending a subcategory to the fully qualified class name, and explicitly specify the category usingLoggerFactory.CreateLogger:
namespace Example{ public class DefaultService : IService { private readonly ILogger _logger; public DefaultService(ILoggerFactory loggerFactory) => _logger = loggerFactory.CreateLogger("Example.DefaultService.CustomCategory"); // ... }}CallingCreateLogger with a fixed name is useful when used in multiple classes/types so the events can be organized by category.
ILogger<T> is equivalent to callingCreateLogger with the fully qualified type name ofT.
The following table lists theLogLevel values, the convenienceLog{LogLevel} extension method, and the suggested usage:
| LogLevel | Value | Method | Description |
|---|---|---|---|
| Trace | 0 | LogTrace | Contain the most detailed messages. These messages might contain sensitive app data. These messages are disabled by default and shouldnot 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. Might 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 can't 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, theLogLevel is listed from lowest to highest severity.
TheLog method's first parameter,LogLevel, indicates the severity of the log. Rather than callingLog(LogLevel, ...), most developers call theLog{LogLevel} extension methods. TheLog{LogLevel} extension methods call theLog method and specify theLogLevel. 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 constantInt32 value.AppLogEvents is a class that exposes various named identifier constants and is displayed in theLog event ID section.
The following code createsInformation andWarning 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 firstLog{LogLevel} parameter,AppLogEvents.Read, is theLog 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 themessage template section later in this article.
Configure the appropriate log level and call the correctLog{LogLevel} methods to control how much log output is written to a particular storage medium. For example:
Trace orDebug levels produces a high-volume of detailed log messages. To control costs and not exceed data storage limits, logTrace andDebug level messages to a high-volume, low-cost data store. Consider limitingTrace andDebug to specific categories.Warning throughCritical levels should produce few log messages.Warning.Trace orDebug messages when troubleshooting. To limit output, setTrace orDebug only for the categories under investigation.The following JSON setsLogging:Console:LogLevel:Microsoft:Information:
{ "Logging": { "LogLevel": { "Microsoft": "Warning" }, "Console": { "LogLevel": { "Microsoft": "Information" } } }}Each log can specify anevent identifier, theEventId is a structure with anId and optionalName readonly properties. The sample source code uses theAppLogEvents class to define event IDs:
using Microsoft.Extensions.Logging;internal static class AppLogEvents{ internal static EventId Create = new(1000, "Created"); internal static EventId Read = new(1001, "Read"); internal static EventId Update = new(1002, "Updated"); internal static EventId Delete = new(1003, "Deleted"); // These are also valid EventId instances, as there's // an implicit conversion from int to an EventId internal const int Details = 3000; internal const int Error = 3001; internal static EventId ReadNotFound = 4000; internal static EventId UpdateNotFound = 4001; // ...}Tip
For more information on converting anint to anEventId, seeEventId.Implicit(Int32 to EventId) Operator.
An event ID associates a set of events. For example, all logs related to reading values from a repository might be1001.
The logging provider might 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 a1b2c3warn: Example.DefaultService.GetAsync[4000] GetAsync(a1b2c3) not foundSome logging providers store the event ID in a field, which allows for filtering on the ID.
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, param2Note
Be mindful when using multiple placeholders within a single message template, as they're ordinal-based. The names aren't used to align the arguments to the placeholders.
This approach lets logging providers implementsemantic 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:
ID andRunTime properties.RunTime range without having to parse the time out of the text message.Log message templates support placeholder formatting. Templates can specifyany valid format for the given type argument. For example, consider the followingInformation logger message template:
_logger.LogInformation("Logged on {PlaceHolderName:MMMM dd, yyyy}", DateTimeOffset.UtcNow);// Logged on January 06, 2022In the preceding example, theDateTimeOffset instance is the type that corresponds to thePlaceHolderName in the logger message template. This name can be anything as the values are ordinal-based. TheMMMM dd, yyyy format is valid for theDateTimeOffset type.
For more information onDateTime andDateTimeOffset formatting, seeCustom date and time format strings.
The following examples show how to format a message template using the{} placeholder syntax. Additionally, an example of escaping the{} placeholder syntax is shown with its output. Finally, string interpolation with templating placeholders is also shown:
logger.LogInformation("Number: {Number}", 1); // Number: 1logger.LogInformation("{{Number}}: {Number}", 3); // {Number}: 3logger.LogInformation($"{{{{Number}}}}: {{Number}}", 5); // {Number}: 5Tip
The logger methods have overloads that take an exception parameter:
public void Test(string id){ try { if (id is "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.
If the default log level isn't set, the default log level value isInformation.
For example, consider the following worker service app:
With the preceding setup, navigating to the privacy or home page produces manyTrace,Debug, andInformation messages withMicrosoft in the category name.
The following code sets the default log level when the default log level isn't set in configuration:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);builder.Logging.SetMinimumLevel(LogLevel.Warning);using IHost host = builder.Build();await host.RunAsync();A filter function is invoked for all providers and categories that don't have rules assigned to them by configuration or code:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);builder.Logging.AddFilter((provider, category, logLevel) =>{ return provider.Contains("ConsoleLoggerProvider") && (category.Contains("Example") || category.Contains("Microsoft")) && logLevel >= LogLevel.Information;});using IHost host = builder.Build();await host.RunAsync();The preceding code displays console logs when the category containsExample orMicrosoft and the log level isInformation or higher.
Ascope groups a set of logical operations. This grouping can 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 ausing block:
public async Task<T> GetAsync<T>(string id){ T result; var transactionId = Guid.NewGuid().ToString(); using (_logger.BeginScope(new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>("TransactionId", transactionId), })) { _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:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);builder.Logging.ClearProviders();builder.Logging.AddSimpleConsole(options => options.IncludeScopes = true);using IHost host = builder.Build();await host.RunAsync();The following code logs inMain by getting anILogger instance from DI after building the host:
using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using Microsoft.Extensions.Logging;using IHost host = Host.CreateApplicationBuilder(args).Build();var logger = host.Services.GetRequiredService<ILogger<Program>>();logger.LogInformation("Host created.");await host.RunAsync();The preceding code relies on two NuGet packages:
Its project file looks similar to the following:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net7.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" /> </ItemGroup></Project>Logging should be so fast that it isn't worth the performance cost of asynchronous code. If a logging datastore 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 aLog method, since theLog 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.
The Logging API doesn't include a scenario to change log levels while an app is running. However, some configuration providers can reload configuration, which takes immediate effect on logging configuration. For example, theFile Configuration Provider reloads logging configuration by default. If you change the configuration in code while an app is running, the app can callIConfigurationRoot.Reload to update the app's logging configuration.
TheILogger<TCategoryName> andILoggerFactory interfaces and implementations are included in most .NET SDKs as implicit package reference. They're also available explicitly in the following NuGet packages when not otherwise implicitly referenced:
For more information about which .NET SDK includes implicit package references, see.NET SDK: table to implicit namespace.
Was this page helpful?
Need help with this topic?
Want to try using Ask Learn to clarify or guide you through this topic?
Was this page helpful?
Want to try using Ask Learn to clarify or guide you through this topic?