.NET 结构化日志:配置、作用域与生产排错
结构化日志把关键上下文保存为独立字段,使日志平台可以按订单号、用户标识或请求链路聚合,而不是依赖全文搜索。日志设计应围绕排错问题和业务事件,而不是简单记录每个方法的进入与离开。
核心原则
- 使用消息模板和命名占位符,避免字符串插值破坏结构化字段。
- 通过 BeginScope 添加 RequestId、TenantId 等请求范围上下文。
- 按 Debug、Information、Warning、Error 的真实语义选择级别,控制生产噪声。
- 记录操作结果、耗时和稳定事件名,不输出密码、令牌或敏感个人信息。
- 异常只在负责处理或结束请求的边界记录一次,并把 Exception 作为独立参数传入。
推荐的实践步骤
在 HTTP 入口建立作用域,把 TraceIdentifier 与业务关联号加入上下文。关键外部调用记录目标服务、耗时、状态和重试次数;业务拒绝通常是 Warning,系统异常是 Error。使用配置按命名空间调节级别,并让日志、指标和分布式追踪共享 trace ID。
using (_logger.BeginScope(new Dictionary
{
["OrderId"] = orderId,
["TraceId"] = Activity.Current?.TraceId.ToString() ?? ""
}))
{
_logger.LogInformation(
"Payment completed in {ElapsedMs} ms", elapsedMs);
}
示例用于说明实现思路,实际项目还应结合所使用的框架版本、部署环境和业务约束进行调整。重要配置要进入版本管理,并在测试环境验证后再发布。
常见误区
- 使用字符串插值生成日志,会失去字段类型和可聚合性。
- 把完整对象序列化进日志,可能产生敏感信息和超大事件。
- 捕获异常后只写一条普通文本而不传 Exception,会丢失堆栈和内部异常链。
上线前检查清单
- 确认“使用消息模板和命名占位符,避免字符串插值破坏结构化字段”已经通过代码审查或运行验证。
- 确认“通过 BeginScope 添加 RequestId、TenantId 等请求范围上下文”已经通过代码审查或运行验证。
- 确认“按 Debug、Information、Warning、Error 的真实语义选择级别,控制生产噪声”已经通过代码审查或运行验证。
- 确认“记录操作结果、耗时和稳定事件名,不输出密码、令牌或敏感个人信息”已经通过代码审查或运行验证。
- 为失败路径、边界条件和回滚方案准备测试或演练记录。
- 上线后观察错误率、延迟和资源消耗,确认变化符合预期。
总结
.NET 结构化日志:配置、作用域与生产排错的关键在于把隐含假设变成可执行的约束,并通过测试、监控和复盘持续验证。先从影响最大的真实场景开始,小步调整并保留回滚能力,通常比一次性大范围改造更安全,也更容易积累可复用的工程经验。