.NET 中 Logger 常被忽视的方法 BeginScope

BeginScope 方法是 .NET 中 ILogger 接口的一部分,用于创建日志记录的作用域(Scope)。这种作用域可以将特定的上下文信息包含在日志中,从而提高日志的可读性和调试效率。

配置日志包含作用域信息

首先,需要在日志配置中启用包含作用域信息。以 appsettings.json 为例,以下是配置示例:

json 复制代码
{
  "Logging": {
    "Console": {
      "IncludeScopes": true,
      "LogLevel": {
        "Default": "Information",
        "Microsoft.AspNetCore": "Warning"
      }
    }
  }
}

在该配置中,IncludeScopes 被设置为 true,这意味着在控制台日志中将包含作用域信息。

在代码中使用 BeginScope

使用 BeginScope 方法在日志中添加上下文信息,如下代码手动显示:

csharp 复制代码
[HttpGet(Name = "Get")]
public string Get()
{
    using (_logger.BeginScope("TenantName {TenantName}", "test"))
    {
        _logger.LogInformation("这是一条测试日志信息");
    }

    return "ok";
}

在这段代码中,我们使用 BeginScope 创建了一个作用域,并设置了一个上下文变量。在这个作用域之内,日志信息将包含这个上下文变量。

将作用域信息 JSON化

通过实现自己的 ILogger 接口,我们可以将作用域信息以 JSON 格式输出:

csharp 复制代码
public class ScopeLogger : ILogger
{
    private readonly string _categoryName;
    private static readonly AsyncLocal<Stack<object>> _scopeStack = new AsyncLocal<Stack<object>>();

    public ScopeLogger(string categoryName)
    {
        _categoryName = categoryName;
    }

    public IDisposable BeginScope<TState>(TState state) where TState : notnull
    {
        if (_scopeStack.Value == null)
        {
            _scopeStack.Value = new Stack<object>();
        }
        _scopeStack.Value.Push(state);

        return new Scope(() => _scopeStack.Value.Pop());
    }

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        var logEntry = new Dictionary<string, object>
        {
            ["Timestamp"] = DateTime.UtcNow,
            ["LogLevel"] = logLevel.ToString(),
            ["Category"] = _categoryName,
            ["Message"] = formatter(state, exception),
            ["Exception"] = exception?.ToString()
        };

        if (_scopeStack.Value != null && _scopeStack.Value.Count > 0)
        {
            var scopes = new List<object>();
            foreach (var scope in _scopeStack.Value)
            {
                scopes.Add(scope);
            }
            logEntry["Scopes"] = scopes;
        }

        var json = JsonSerializer.Serialize(logEntry, new JsonSerializerOptions { WriteIndented = true });
        Console.WriteLine(json);
    }

    private class Scope : IDisposable
    {
        private readonly Action _onDispose;

        public Scope(Action onDispose)
        {
            _onDispose = onDispose;
        }

        public void Dispose()
        {
            _onDispose?.Invoke();
        }
    }
}

public class ScopeLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string categoryName)
    {
        return new ScopeLogger(categoryName);
    }

    public void Dispose() { }
}

builder.Logging.ClearProviders();
builder.Logging.AddProvider(new ScopeLoggerProvider());

上面的代码创建了自己的 ILogger 实现,并将作用域信息以 JSON 格式输出。