AgentSkillsCN

extensions-logging

Microsoft.Extensions.Logging 与 LoggerMessage 源代码生成器的使用指南。适用场景:ILogger 抽象、LoggerMessage 源代码生成的日志记录、带有事件 ID 的结构化日志记录、日志过滤与配置、高性能日志记录模式。不适用于:Serilog 特定的接收器与丰富器(应使用 serilog)、NLog 特定的目标与路由(应使用 nlog)、OpenTelemetry 日志导出(应使用 otlp-logging)。

SKILL.md
--- frontmatter
name: extensions-logging
description: >
  Guidance for Microsoft.Extensions.Logging and LoggerMessage source generators.
  USE FOR: ILogger abstraction, LoggerMessage source-generated logging, structured logging with event IDs, log filtering and configuration, high-performance logging patterns.
  DO NOT USE FOR: Serilog-specific sinks and enrichers (use serilog), NLog-specific targets and routing (use nlog), OpenTelemetry log export (use otlp-logging).
license: MIT
metadata:
  displayName: "Microsoft.Extensions.Logging"
  author: "Tyler-R-Kendrick"
  version: "1.0.0"
compatibility:
  - claude
  - copilot
  - cursor

Microsoft.Extensions.Logging

Overview

Microsoft.Extensions.Logging is the built-in logging abstraction for .NET. It provides the ILogger, ILogger<T>, and ILoggerFactory interfaces that decouple application logging from the underlying provider (Console, Debug, EventLog, or third-party sinks like Serilog and NLog). Starting with .NET 6, the LoggerMessage source generator enables compile-time generation of high-performance, strongly-typed logging methods that avoid boxing and string allocation at the call site.

This abstraction is integrated into the generic host, ASP.NET Core, and all Microsoft.Extensions.* libraries, making it the default choice for any .NET application.

Basic ILogger Usage

Inject ILogger<T> through constructor injection. The type parameter sets the log category name.

csharp
using Microsoft.Extensions.Logging;

namespace MyApp.Services;

public class OrderService
{
    private readonly ILogger<OrderService> _logger;

    public OrderService(ILogger<OrderService> logger)
    {
        _logger = logger;
    }

    public void ProcessOrder(string orderId, decimal total)
    {
        _logger.LogInformation(
            "Processing order {OrderId} with total {Total:C}",
            orderId, total);

        try
        {
            // Business logic here
            _logger.LogDebug(
                "Order {OrderId} validated successfully", orderId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex,
                "Failed to process order {OrderId}", orderId);
            throw;
        }
    }
}

LoggerMessage Source Generator

The source generator creates optimized logging methods at compile time. Each method avoids boxing, string interpolation, and allocation when the log level is disabled.

csharp
using Microsoft.Extensions.Logging;

namespace MyApp.Logging;

public static partial class LogMessages
{
    [LoggerMessage(
        EventId = 1001,
        Level = LogLevel.Information,
        Message = "Processing order {OrderId} for customer {CustomerId}")]
    public static partial void OrderProcessing(
        ILogger logger, string orderId, string customerId);

    [LoggerMessage(
        EventId = 1002,
        Level = LogLevel.Information,
        Message = "Order {OrderId} completed in {ElapsedMs}ms")]
    public static partial void OrderCompleted(
        ILogger logger, string orderId, long elapsedMs);

    [LoggerMessage(
        EventId = 2001,
        Level = LogLevel.Error,
        Message = "Order {OrderId} failed")]
    public static partial void OrderFailed(
        ILogger logger, string orderId, Exception exception);

    [LoggerMessage(
        EventId = 3001,
        Level = LogLevel.Warning,
        Message = "Inventory low for product {ProductId}: {Remaining} units")]
    public static partial void InventoryLow(
        ILogger logger, string productId, int remaining);
}

Use the generated methods:

csharp
using Microsoft.Extensions.Logging;
using MyApp.Logging;

namespace MyApp.Services;

public class OrderProcessor
{
    private readonly ILogger<OrderProcessor> _logger;

    public OrderProcessor(ILogger<OrderProcessor> logger)
    {
        _logger = logger;
    }

    public void Process(string orderId, string customerId)
    {
        LogMessages.OrderProcessing(_logger, orderId, customerId);
        var sw = System.Diagnostics.Stopwatch.StartNew();

        try
        {
            // Process order...
            sw.Stop();
            LogMessages.OrderCompleted(
                _logger, orderId, sw.ElapsedMilliseconds);
        }
        catch (Exception ex)
        {
            LogMessages.OrderFailed(_logger, orderId, ex);
            throw;
        }
    }
}

Log Scopes

Scopes attach contextual properties to all log entries within a block, which is essential for correlating logs in distributed systems.

csharp
using Microsoft.Extensions.Logging;

namespace MyApp.Services;

public class PaymentService
{
    private readonly ILogger<PaymentService> _logger;

    public PaymentService(ILogger<PaymentService> logger)
    {
        _logger = logger;
    }

    public async Task ProcessPaymentAsync(
        string orderId, string transactionId)
    {
        using (_logger.BeginScope(
            new Dictionary<string, object>
            {
                ["OrderId"] = orderId,
                ["TransactionId"] = transactionId
            }))
        {
            _logger.LogInformation("Payment initiated");
            // All logs within this scope include
            // OrderId and TransactionId
            await ChargeAsync();
            _logger.LogInformation("Payment completed");
        }
    }

    private Task ChargeAsync() => Task.CompletedTask;
}

Configuration and Filtering

Configure log levels per category in appsettings.json:

json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.EntityFrameworkCore": "Warning",
      "MyApp.Services": "Debug",
      "MyApp.Services.PaymentService": "Trace"
    },
    "Console": {
      "LogLevel": {
        "Default": "Information"
      },
      "FormatterName": "json",
      "FormatterOptions": {
        "IncludeScopes": true,
        "TimestampFormat": "yyyy-MM-dd HH:mm:ss "
      }
    }
  }
}

Register providers programmatically:

csharp
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

var builder = WebApplication.CreateBuilder(args);

builder.Logging
    .ClearProviders()
    .AddConsole()
    .AddDebug()
    .SetMinimumLevel(LogLevel.Debug)
    .AddFilter("Microsoft", LogLevel.Warning)
    .AddFilter<Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider>(
        "MyApp", LogLevel.Debug);

var app = builder.Build();
app.Run();

Log Level Reference

LevelValueUse Case
Trace0Detailed diagnostic data, never in production
Debug1Developer-facing diagnostic information
Information2General application flow events
Warning3Abnormal or unexpected events that do not cause failure
Error4Failures in the current operation (not app-wide)
Critical5Application-wide failures requiring immediate attention
None6Disables logging for a category

LoggerMessage vs Manual Logging

AspectLoggerMessage Source GenManual _logger.LogX(...)
PerformanceZero-alloc when level disabledAllocates params array
BoxingAvoided via generated codeOccurs for value types
Compile-time validationMessage template checkedRuntime only
Event ID managementEnforced by attributeEasy to forget
Code organizationCentralized in partial classScattered across codebase
DiscoverabilitySearchable by event IDGrep for string patterns

Best Practices

  1. Use LoggerMessage source generators for all logging in hot paths and production services to eliminate allocation overhead when the target log level is disabled.
  2. Assign unique EventId values to each log message and document them; event IDs enable precise log filtering and alerting without relying on fragile string matching.
  3. Use structured log templates ("Order {OrderId} processed") instead of string interpolation ($"Order {orderId} processed") so log aggregators can index and query named properties.
  4. Configure log levels per category in appsettings.json rather than in code, so operations teams can adjust verbosity without redeploying the application.
  5. Use BeginScope to attach correlation IDs, request IDs, and tenant IDs to all log entries within a logical operation for distributed tracing.
  6. Never log sensitive data (passwords, tokens, PII) even at Trace level; use the [LogProperties(OmitReferenceName = true)] attribute or redact explicitly.
  7. Set Microsoft.AspNetCore and Microsoft.EntityFrameworkCore to Warning or higher in production to reduce noise from framework-internal logs.
  8. Organize LoggerMessage methods in a single static partial class per bounded context (e.g., OrderLogMessages, PaymentLogMessages) with contiguous event ID ranges.
  9. Test that critical error paths produce the expected log entries by injecting Microsoft.Extensions.Logging.Testing.FakeLogger or a similar test double in unit tests.
  10. Prefer ILogger<T> over ILogger for constructor injection because the generic version automatically sets the category name to the fully-qualified type name.