Skip to content

A Brief Introduction to .NET Default Logger and Optimization Techniques

TLDR

  • .NET integrates Console, Debug, and EventSource providers by default, which can be customized via builder.Logging.
  • appsettings.json allows flexible control over log levels for different namespaces and providers, supporting the * wildcard.
  • For high-frequency or resource-intensive logs, prioritize using logger.IsEnabled for pre-checks to avoid unnecessary computations.
  • Structured Logging avoids performance overhead from string concatenation and facilitates parsing by systems like ELK.
  • The [LoggerMessage] Source Generator is recommended; it automatically generates strongly-typed delegates, eliminates object[] allocation and boxing costs, and includes built-in IsEnabled checks.

Sample Project

Executable sample for this article: CloudyWing/DotNetLoggingSample.

Log Levels

During development and maintenance, choose the appropriate Log Level based on the context to facilitate filtering and debugging in production environments.

LevelValueMethodDescription
Trace0LogTraceMost detailed messages; disabled by default; should not be enabled in production.
Debug1LogDebugUsed for development debugging; use with caution in production.
Information2LogInformationTracks general application flow.
Warning3LogWarningHandles abnormal or unexpected events that do not affect program operation.
Error4LogErrorUnhandled exceptions.
Critical5LogCriticalSevere failures requiring immediate attention.
None6N/ADisables all logs entirely.

Basic Injection and Configuration

WebApplication.CreateBuilder() loads Console, Debug, and EventSource providers by default. If adjustments are needed, they can be configured via builder.Logging.

When to encounter this issue: When you need to customize log output targets or clear default providers.

csharp
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Clear default providers and reconfigure
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();

Controlling Log Levels via Appsettings.json

You can achieve fine-grained control by setting log levels for different namespaces (Categories) through configuration files.

When to encounter this issue: When you want to hide detailed logs for specific services in production, keeping only Warning level and above.

json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "LoggerTest.Services.TestService1": "Warning"
    }
  }
}
  • To apply the same settings to multiple namespaces, use wildcards, e.g., "*.Services": "Warning".

Improving Performance with Logger.IsEnabled

Before performing expensive operations (such as database queries), check if the log level is enabled.

When to encounter this issue: When log content requires complex calculations or large data processing to be generated.

csharp
if (logger.IsEnabled(LogLevel.Information)) {
    int processedRecords = await database.GetProcessedRecordsCount();
    logger.LogInformation("System has finished data update, processed {Count} records.", processedRecords);
}

Advantages of Structured Logging

Structured logging separates templates from parameters, avoiding runtime string concatenation and outputting data in JSON format, which facilitates subsequent analysis.

When to encounter this issue: When you need to import logs into ELK or other centralized log analysis systems.

csharp
// Correct structured logging approach
logger.LogInformation("User {UserId} has logged into the system", user.Id);

// Output in JSON format
builder.Logging.AddJsonConsole();

Using LoggerMessage.Define and Source Generator

Traditional LogInformation calls incur object[] allocation and boxing costs. Using the [LoggerMessage] Source Generator generates efficient, strongly-typed delegates at compile time.

When to encounter this issue: When the application has high performance requirements and a large volume of log output.

Example of using [LoggerMessage]

csharp
public partial class UserService {
    private readonly ILogger<UserService> logger;

    public UserService(ILogger<UserService> logger) => this.logger = logger;

    public void Login(User user) => LogUserLoggedIn(user.Id, user.Department);

    [LoggerMessage(
        EventId = 1001,
        Level = LogLevel.Information,
        Message = "User {UserId} has logged into the system, department: {Department}"
    )]
    private partial void LogUserLoggedIn(string userId, string department);
}
  • Performance Advantage: Automatically generates IsEnabled checks at compile time, the call path has no object[] allocation, and there is no boxing cost.
  • Naming Suggestion: Use verb phrases starting with Log. The level is controlled by the Attribute, and parameter names must match the {Placeholder} in the Message template.

Change Log

  • 2025-03-23 Initial document creation.
  • 2026-05-27 Added "Using LoggerMessage.Define" and "Using LoggerMessage Source Generator" sections, and included a link to the sample project.