首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >日志追踪革命!.NET 中关联ID的终极实现方案

日志追踪革命!.NET 中关联ID的终极实现方案

作者头像
郑子铭
发布2025-08-24 11:02:50
发布2025-08-24 11:02:50
2150
举报

场景:

假设你有一个执行关键任务的应用程序,并添加了日志记录:

代码语言:javascript
复制
internal sealed class Handler(ILogger<Handler> logger) : IHandler
{
    public async Task InvokeAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("处理器 {HandlerName} 开始工作", nameof(Handler));
        
        // 执行异步操作
        await Task.Delay(TimeSpan.FromMicroseconds(), cancellationToken);
        
        logger.LogInformation("处理器 {HandlerName} 完成工作", nameof(Handler));
    }
}

标准日志输出

代码语言:javascript
复制
info: LevelUpLogs.Handler[0]
      处理器 Handler 开始工作
info: LevelUpLogs.Handler[0]
      处理器 Handler 完成工作

核心问题: 当需要追踪特定请求时,现有日志缺乏关联标识,无法跨条目跟踪完整链路。


✅ 解决方案 1:启用日志范围

appsettings.json 开启 IncludeScopes

代码语言:javascript
复制
{
  "Logging": {
    "Console": {
      "IncludeScopes": true // <-- 关键配置
    }
  }
}

优化后日志

代码语言:javascript
复制
info: LevelUpLogs.Handler[0]
      => SpanId:64b2903e475a475b, TraceId:cfccdb7d8f91e4a3468abd1e870cd469...
      处理器 Handler 开始工作
info: LevelUpLogs.Handler[0]
      => SpanId:64b2903e475a475b, TraceId:cfccdb7d8f91e4a3468abd1e870cd469...
      处理器 Handler 完成工作

优势:通过 SpanId/TraceId 实现请求链路追踪


🚀 解决方案 2:自定义关联ID系统

步骤 1:创建关联ID提供器
代码语言:javascript
复制
internal interfaceICorrelationIdProvider
{
    string? CorrelationId { get; set; }
}

internalsealedclassCorrelationIdProvider : ICorrelationIdProvider
{
    publicstring? CorrelationId { get; set; }
}

// 注册为作用域服务
builder.Services.AddScoped<ICorrelationIdProvider, CorrelationIdProvider>();
步骤 2:创建中间件获取ID
代码语言:javascript
复制
internal sealed class CorrelationIdMiddleware(RequestDelegate next)
{
    public async Task Invoke(HttpContext context, ICorrelationIdProvider provider)
    {
        conststring HeaderName = "X-Correlation-ID";
        
        // 从请求头获取或生成新ID
        context.Request.Headers.TryGetValue(HeaderName, outvar headerValue);
        provider.CorrelationId = !string.IsNullOrEmpty(headerValue) 
            ? headerValue.ToString() 
            : Guid.NewGuid().ToString();
        
        await next(context);
    }
}
步骤 3:在处理器中使用
代码语言:javascript
复制
internal sealed class Handler(
    ILogger<Handler> logger,
    ICorrelationIdProvider provider) : IHandler
{
    public async Task InvokeAsync(CancellationToken ct)
    {
        // 创建日志范围
        using (logger.BeginScope(provider.CorrelationId!)) 
        {
            logger.LogInformation("处理器 {HandlerName} 开始工作", nameof(Handler));
            await Task.Delay(TimeSpan.FromMicroseconds(), ct);
            logger.LogInformation("处理器 {HandlerName} 完成工作", nameof(Handler));
        }
    }
}

日志输出

代码语言:javascript
复制
info: LevelUpLogs.Handler[0]
      => ... => 255fb6aa-4cae-4fc4-876a-35e0c059bb8a
      处理器 Handler 开始工作
info: LevelUpLogs.Handler[0]
      => ... => 255fb6aa-4cae-4fc4-876a-35e0c059bb8a
      处理器 Handler 完成工作

⚡ 终极方案:高性能日志优化

步骤 1:关闭范围包含
代码语言:javascript
复制
{
  "Logging": {
    "Console": {
      "IncludeScopes": false // 禁用范围输出
    }
  }
}
步骤 2:使用 LoggerMessage.Define
代码语言:javascript
复制
internal sealed class Handler(ILogger<Handler> logger, ICorrelationIdProvider provider) : IHandler
{
    privateconstint _eventId = ;
    
    // 预编译日志模板
    privatestaticreadonly Action<ILogger, string, string, Exception?> _logStart = 
        LoggerMessage.Define<string, string>(LogLevel.Information,
            new EventId(_eventId, nameof(LogStart)),
            "[{CorrelationId}] 处理器 {HandlerName} 开始工作");
    
    privatestaticreadonly Action<ILogger, string, string, Exception?> _logEnd = 
        LoggerMessage.Define<string, string>(LogLevel.Information,
            new EventId(_eventId, nameof(LogEnd)),
            "[{CorrelationId}] 处理器 {HandlerName} 完成工作");
    
    public async Task InvokeAsync(CancellationToken ct)
    {
        // 高性能日志调用
        _logStart(logger, provider.CorrelationId!, nameof(Handler), null);
        await Task.Delay(TimeSpan.FromMicroseconds(), ct);
        _logEnd(logger, provider.CorrelationId!, nameof(Handler), null);
    }
}

优化后日志

代码语言:javascript
复制
info: LevelUpLogs.Handler[100]
      [43f26b8e-e9a8-411b-9865-f88ad667d59c] 处理器 Handler 开始工作
info: LevelUpLogs.Handler[100]
      [43f26b8e-e9a8-411b-9865-f88ad667d59c] 处理器 Handler 完成工作

三大优势

  1. 1. 关联ID直接嵌入日志消息
  2. 2. EventId精准定位代码位置
  3. 3. 零分配开销的高性能日志

架构价值

方案

追踪能力

性能

可读性

基础日志

⭐⭐⭐⭐

⭐⭐

日志范围

⭐⭐⭐

⭐⭐⭐

关联ID+日志范围

⭐⭐⭐⭐

⭐⭐⭐

⭐⭐

关联ID+预编译

⭐⭐⭐⭐⭐

⭐⭐⭐⭐⭐

⭐⭐⭐⭐⭐


💬 开发者问答

Q:何时必须使用关联ID? A:当你的应用涉及:

  • • 微服务间调用链追踪
  • • 支付/订单等关键业务流水
  • • 需要分钟级定位问题的生产系统

Q:两种方案如何选择?

  • • 快速实现:选择日志范围方案(方案1)
  • • 高性能场景:采用预编译日志+关联ID(终极方案)
代码语言:javascript
复制
点击下方卡片关注DotNet NB
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DotNet NB 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景:
  • ✅ 解决方案 1:启用日志范围
  • 🚀 解决方案 2:自定义关联ID系统
    • 步骤 1:创建关联ID提供器
    • 步骤 2:创建中间件获取ID
    • 步骤 3:在处理器中使用
  • ⚡ 终极方案:高性能日志优化
    • 步骤 1:关闭范围包含
    • 步骤 2:使用 LoggerMessage.Define
  • 架构价值
  • 💬 开发者问答
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档