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 时的决策路径:

Tag 与 Log 决策流程图 需要记录什么数据? 需要跨 Span 过滤/聚合? 是 否 使用 Tag Key:Value 结构化 仅单 Span 上下文 需要进一步判断 是事件/堆栈/ 调试细节? 是 使用 Log 带时间戳事件 否 考虑 Tag 或重新设计 决策要点 1. 优先用 Tag 承载可过滤、可聚合的属性,降低 Log 存储压力 2. 异常堆栈、请求体、重试记录等一次性细节,放入 Log

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% 以上。