假设你有一个执行关键任务的应用程序,并添加了日志记录:
internal sealed class Handler(ILogger<Handler> logger) : IHandler
{
public async Task InvokeAsync(CancellationToken cancellationToken)
{
logger.LogInformation("处理器 {HandlerName} 开始工作", nameof(Handler));
// 执行异步操作
await Task.Delay(TimeSpan.FromMicroseconds(2), cancellationToken);
logger.LogInformation("处理器 {HandlerName} 完成工作", nameof(Handler));
}
}
标准日志输出:
info: LevelUpLogs.Handler[0]
处理器 Handler 开始工作
info: LevelUpLogs.Handler[0]
处理器 Handler 完成工作
核心问题:
当需要追踪特定请求时,现有日志缺乏关联标识,无法跨条目跟踪完整链路。
在 appsettings.json
开启 IncludeScopes
:
{
"Logging": {
"Console": {
"IncludeScopes": true // <-- 关键配置
}
}
}
优化后日志:
info: LevelUpLogs.Handler[0]
=> SpanId:64b2903e475a475b, TraceId:cfccdb7d8f91e4a3468abd1e870cd469...
处理器 Handler 开始工作
info: LevelUpLogs.Handler[0]
=> SpanId:64b2903e475a475b, TraceId:cfccdb7d8f91e4a3468abd1e870cd469...
处理器 Handler 完成工作
优势:通过 SpanId/TraceId 实现请求链路追踪
internal interface ICorrelationIdProvider
{
string? CorrelationId { get; set; }
}
internal sealed class CorrelationIdProvider : ICorrelationIdProvider
{
public string? CorrelationId { get; set; }
}
// 注册为作用域服务
builder.Services.AddScoped<ICorrelationIdProvider, CorrelationIdProvider>();
internal sealed class CorrelationIdMiddleware(RequestDelegate next)
{
public async Task Invoke(HttpContext context, ICorrelationIdProvider provider)
{
const string HeaderName = "X-Correlation-ID";
// 从请求头获取或生成新ID
context.Request.Headers.TryGetValue(HeaderName, out var headerValue);
provider.CorrelationId = !string.IsNullOrEmpty(headerValue)
? headerValue.ToString()
: Guid.NewGuid().ToString();
await next(context);
}
}
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(2), ct);
logger.LogInformation("处理器 {HandlerName} 完成工作", nameof(Handler));
}
}
}
日志输出:
info: LevelUpLogs.Handler[0]
=> ... => 255fb6aa-4cae-4fc4-876a-35e0c059bb8a
处理器 Handler 开始工作
info: LevelUpLogs.Handler[0]
=> ... => 255fb6aa-4cae-4fc4-876a-35e0c059bb8a
处理器 Handler 完成工作
{
"Logging": {
"Console": {
"IncludeScopes": false // 禁用范围输出
}
}
}
internal sealed class Handler(ILogger<Handler> logger, ICorrelationIdProvider provider) : IHandler
{
private const int _eventId = 100;
// 预编译日志模板
private static readonly Action<ILogger, string, string, Exception?> _logStart =
LoggerMessage.Define<string, string>(LogLevel.Information,
new EventId(_eventId, nameof(LogStart)),
"[{CorrelationId}] 处理器 {HandlerName} 开始工作");
private static readonly 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(2), ct);
_logEnd(logger, provider.CorrelationId!, nameof(Handler), null);
}
}
优化后日志:
info: LevelUpLogs.Handler[100]
[43f26b8e-e9a8-411b-9865-f88ad667d59c] 处理器 Handler 开始工作
info: LevelUpLogs.Handler[100]
[43f26b8e-e9a8-411b-9865-f88ad667d59c] 处理器 Handler 完成工作
三大优势:
- 关联ID直接嵌入日志消息
- EventId精准定位代码位置
- 零分配开销的高性能日志
| 方案 | 追踪能力 | 性能 | 可读性 |
|-------------------|-------------|---------|-----------|
| 基础日志 | ❌ | ⭐⭐⭐⭐ | ⭐⭐ |
| 日志范围 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐ |
| 关联ID+日志范围 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| 关联ID+预编译 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
Q:何时必须使用关联ID?
A:当你的应用涉及:
Q:两种方案如何选择?