Logging

Logging in C# - .NET
Learn about app logging provided by the Microsoft.Extensions.Logging NuGet package in C#.
https://learn.microsoft.com/en-us/dotnet/core/extensions/logging?tabs=command-line

Minimal template

This example is weak since it is not scaleable, performant, or configurable.

This sample console app relies on the following NuGet packages:

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");
  1. Create an ILoggerFactory here we set it so logs are written to the console - we call the place we write to a sink
  1. Create the logger, and set the category to program ; a category is just a grouping
  1. Log at information level, with a template where Description is set to fun

Logging a method call example

[LoggerMessage(LogLevel.Information, "MethodName was called")]
public static partial void MethodName(ILogger logger);

Advanced Logging

Typically most serious applications don’t use the Microsoft logging, and instead continue to use the ILogger API and use a third-party implementation like OTel for sending metrics off to Grafana and Serilog for structured logging.

The Generic and WebApp host both supply a DI ILogger , so no need to register just grab it like so.

partial class ExampleHandler(ILogger<ExampleHandler> logger)
{
    public void HandleRequest()
    {
        LogHandleRequest(logger);
    }

    [LoggerMessage(LogLevel.Information, "ExampleHandler.HandleRequest was called")]
    public static partial void LogHandleRequest(ILogger logger) 
    {
	    logger.LogInformation("Hello World! Logging is {Description}.", "fun");
    }
}
  1. The primary constructor takes ILogger<TCategoryName>, if no logger exists with that category yet, a new one is created

Configuration

For hosted applications, configs are normally set in appsettings.env.json , within “Logging”

So to be clear, you are setting the minimum logging level based on the category

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

You can set logging levels based on provider.

Note a provider is where you output your logs to, so debug is your debug window.

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

Log Levels

0 is the lowest, 6 is the highest priority

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

LogLevelValueMethodDescription
Trace0LogTraceContain the most detailed messages. These messages might contain sensitive app data. These messages are disabled by default and should not be enabled in production.
Debug1LogDebugFor debugging and development. Use with caution in production due to the high volume.
Information2LogInformationTracks the general flow of the app. Might have long-term value.
Warning3LogWarningFor abnormal or unexpected events. Typically includes errors or conditions that don't cause the app to fail.
Error4LogErrorFor errors and exceptions that can't be handled. These messages indicate a failure in the current operation or request, not an app-wide failure.
Critical5LogCriticalFor failures that require immediate attention. Examples: data loss scenarios, out of disk space.
None6Specifies that no messages should be written.

Event Ids

You can use event Ids to signal certain events, such as a read, or a certain failure.

using Microsoft.Extensions.Logging;

internal static class AppLogEvents
{
    internal static readonly EventId Read = new(1001, "Read");
    internal static readonly EventId ReadNotFound = new(4000, "ReadNotFound");
}

public sealed class AccountRepository(ILogger<AccountRepository> logger)
{
    public string? GetById(string accountId)
    {
        logger.LogInformation(AppLogEvents.Read, "Reading account {AccountId}", accountId);
	      logger.LogWarning(AppLogEvents.ReadNotFound, "Account {AccountId} was not found", accountId);
    }
}

using Microsoft.Extensions.Logging;

internal static partial class RepoLog
{
    [LoggerMessage(
        EventId = 1001,
        Level = LogLevel.Information,
        Message = "Reading account {AccountId}")]
    internal static partial void ReadingAccount(ILogger logger, string accountId);

    [LoggerMessage(
        EventId = 4000,
        Level = LogLevel.Warning,
        Message = "Account {AccountId} was not found")]
    internal static partial void AccountNotFound(ILogger logger, string accountId);
}

public sealed class AccountRepository(ILogger<AccountRepository> logger)
{
    public string? GetById(string accountId)
    {
        RepoLog.ReadingAccount(logger, accountId);
            RepoLog.AccountNotFound(logger, accountId);
    }
}