4. Span标签(Tags)与日志(Logs)的区别:何时用Tag,何时用Log,以及它们对MTTR复盘的影响
在分布式追踪的实践中,Span 的 Tags(标签) 和 Logs(日志) 是两种核心的元数据承载方式。很多团队在 MTTR 复盘时发现,数据要么冗余、要么缺失,根本原因在于没有区分两者的语义边界。本节从数据结构、查询效率、MTTR 复盘场景三个维度,给出明确的划分标准。
4.1 核心区别:结构化属性 vs. 时序事件
| 维度 | Tags(标签) | Logs(日志) |
|---|---|---|
| 本质 | Span 的键值对属性,描述请求的静态或状态性特征 | Span 生命周期内的离散事件,带时间戳 |
| 数据结构 | Key:Value,Value 类型固定(string / number / bool) | Timestamp + Key:Value,Value 可包含复杂结构 |
| 查询方式 | 支持精确过滤和聚合(如:http.status_code = 500) | 支持全文检索和时间范围扫描 |
| 存储开销 | 低,通常作为索引字段 | 高,每条日志独立存储 |
| 典型用途 | 服务名、HTTP 方法、状态码、错误类型、用户 ID | 异常堆栈、SQL 语句、请求体、调试信息 |
MTTR 复盘关键原则: 需要跨 Span 过滤、聚合、分组的数据,必须用 Tag;仅用于单 Span 上下文排查的细节,用 Log。
4.2 何时用 Tag:可过滤、可聚合、可排序
Tag 的设计目标是让运维人员能够快速缩小问题范围。在 MTTR 复盘流程中,以下场景必须使用 Tag:
- 错误分类与聚合:
error=true、error.type=TimeoutException、http.status_code=502。复盘时直接按 error.type 分组,定位高频错误。 - 服务拓扑与依赖:
peer.service=payment-svc、span.kind=client。用于构建调用链拓扑,识别故障扩散路径。 - 业务维度筛选:
order.id=ORD-12345、user.region=us-east-1。支持按业务实体快速检索相关 Trace。 - 性能基线标记:
db.statement=SELECT * FROM orders、messaging.destination=order.queue。用于聚合慢查询或延迟热点。
// 正确使用 Tag 的示例(Go OpenTelemetry)
span.SetAttributes(
attribute.String("http.method", "POST"),
attribute.Int("http.status_code", 500),
attribute.Bool("error", true),
attribute.String("error.type", "DatabaseTimeout"),
attribute.String("peer.service", "mysql-shard-01"),
)
4.3 何时用 Log:事件细节、异常堆栈、调试上下文
Log 用于记录 Span 执行过程中发生的瞬时事件。这些数据通常只在查看单个 Span 详情时才有意义,不适合全局过滤。
- 异常堆栈:
exception.stacktrace是 Log 的经典场景,堆栈信息长且唯一,不适合作为 Tag 存储。 - 请求/响应体:调试时查看 payload 内容,但不应作为过滤条件。
- 重试与补偿记录:如“第 2 次重试成功”、“回滚事务 ID=txn-xxx”。
- 中间计算结果:算法或业务逻辑中的临时变量,仅在排查特定 Bug 时需要。
// 正确使用 Log 的示例(Java OpenTelemetry)
span.addEvent("RetryAttempt", Attributes.of(
attribute.Long("attempt", 2),
attribute.String("reason", "Connection reset"),
attribute.String("fallback.result", "success")
));
span.addEvent("exception", Attributes.of(
attribute.String("exception.type", "java.sql.SQLTimeoutException"),
attribute.String("exception.message", "Timeout after 30s"),
attribute.String("exception.stacktrace", stackTraceString)
));
4.4 对 MTTR 复盘的影响:错误使用导致复盘效率下降
以下表格总结了 Tag 和 Log 误用对 MTTR 各阶段的典型影响:
| MTTR 阶段 | Tag 误用为 Log | Log 误用为 Tag |
|---|---|---|
| 发现(Detect) | 无法按错误类型聚合告警,告警延迟增加 3-5 分钟 | Tag 基数爆炸,索引性能下降,告警查询超时 |
| 定位(Triage) | 需要逐个 Span 展开查看 Log 才能判断错误类型,定位时间翻倍 | 大量无用 Tag 干扰过滤结果,误判根因服务 |
| 诊断(Diagnose) | 缺少结构化错误码,无法快速关联上下游 | 堆栈信息被截断或丢失,诊断深度不足 |
| 修复(Fix) | 复盘报告缺少聚合数据,无法量化影响范围 | 存储成本飙升,历史数据保留周期被迫缩短 |
常见反模式: 将
exception.stacktrace 整个字符串写入 Tag。这会导致 Tag 值超长、索引膨胀、查询缓慢。正确的做法是将 error.type 和 error.message 作为 Tag,堆栈放入 Log。
4.5 决策流程:Tag 还是 Log?
以下 SVG 流程图展示了在编写 Span 时的决策路径:
4.6 标准化建议:Tag 与 Log 的命名规范
为了在 MTTR 复盘时能够快速理解数据,建议团队遵循以下命名约定:
| 类别 | 推荐 Tag Key | 推荐 Log Event Name | 说明 |
|---|---|---|---|
| HTTP | http.method, http.status_code, http.url |
http.request.headers, http.response.body |
状态码、方法用 Tag;请求体用 Log |
| 数据库 | db.system, db.statement, db.operation |
db.query.parameters, db.rows.affected |
SQL 模板用 Tag;参数值用 Log |
| 消息队列 | messaging.destination, messaging.operation |
message.payload, message.retry.count |
队列名、操作类型用 Tag;消息体用 Log |
| 错误 | error, error.type, error.message |
exception (含 stacktrace) |
错误类型和消息用 Tag;堆栈用 Log |
复盘最佳实践: 在 MTTR 复盘报告中,先通过 Tag 聚合出“错误类型 Top 5”和“受影响服务 Top 3”,再针对每个聚合组,查看典型 Trace 的 Log 细节。这种“先 Tag 后 Log”的排查路径,可将平均诊断时间缩短 40% 以上。