4. 核心数据模型:Log(日志)的定义、与 Trace/Metric 的关联、LogRecord 结构

在可观测性三大支柱中,Log(日志) 是最古老、最广泛使用的数据形式。OpenTelemetry 并未重新发明日志,而是定义了 Log 数据模型 的标准结构,并提供了将日志与 Trace、Metric 关联的机制,从而打破传统日志孤岛,实现真正的可观测性融合。

4.1 什么是 OTel 中的 Log?

在 OpenTelemetry 语境下,Log 是一条带有时间戳的、记录系统或应用程序中发生事件的文本记录。与传统日志不同,OTel 的 Log 数据模型是结构化的,它强制要求日志记录包含一组标准属性,使其可以被机器解析、关联和分析。

核心转变: 从“非结构化文本”到“结构化事件”。OTel 不要求你停止写日志,而是要求你按照 LogRecord 的规范来记录日志,从而让日志成为可观测性数据的一部分,而非孤立文本。

4.2 Log 与 Trace / Metric 的关联

这是 OTel 日志模型最具价值的部分。通过标准属性,日志可以无缝关联到 Trace 和 Metric,形成完整的可观测性拼图。

关联维度 关键属性 关联方式与价值
Log ↔ Trace trace_id, span_id 日志记录中包含当前请求的 Trace ID 和 Span ID。当排查问题时,可以从一条错误日志直接跳转到对应的分布式调用链,查看完整的请求上下文。
Log ↔ Metric metric.name, metric.value (通过 LogRecord 的 body 或 attributes) 日志可以记录指标的瞬时值或事件。例如,记录“订单创建成功”日志时,可以同时携带订单金额作为属性,后续通过日志分析聚合出“总订单金额”指标。
Log ↔ Resource resource.* (如 service.name, host.name) 所有 LogRecord 都关联一个 Resource 对象,标识日志来源的服务、主机、容器等。这是跨服务日志聚合和过滤的基础。
实战案例: 当用户下单失败时,系统输出一条错误日志。该日志包含 trace_id: "abc123" 和 span_id: "def456"。通过 OTel 后端,你可以:
  1. 从这条日志出发,直接查询 Trace ID 为 abc123 的完整调用链。
  2. 在调用链中看到该 Span 的详细属性,如请求参数、数据库查询耗时。
  3. 同时,该日志的 service.name 属性让你知道是哪个微服务出了问题。
这种关联能力将日志从“静态文本”升级为“动态可导航的线索”。

4.3 LogRecord 结构详解

LogRecord 是 OTel 日志数据模型的核心对象。它定义了日志记录必须包含的字段,确保不同语言和框架产生的日志具有一致的结构。

下图展示了 LogRecord 的核心字段及其在可观测性体系中的位置:

LogRecord 核心结构 LogRecord Timestamp (必选) Severity (必选) Body (必选) TraceId (关联) SpanId (关联) TraceFlags (关联) Resource (上下文) InstrumentationScope Attributes (KV对) 字段说明 • Timestamp: 事件发生时间(Unix 纳秒时间戳) • Severity: 日志级别(TRACE, DEBUG, INFO, WARN, ERROR, FATAL) • Body: 日志正文(字符串或结构化数据) • Attributes: 可扩展的键值对,用于携带任意上下文信息

4.4 LogRecord 字段速查表

字段名 类型 必需性 说明
Timestamp UnixNano 是 日志事件发生的精确时间戳,纳秒精度。
ObservedTimestamp UnixNano 否 日志被观测到的时间,通常与 Timestamp 相同,用于处理异步日志场景。
SeverityNumber int 是 严重程度的数字表示(1=TRACE, 5=DEBUG, 9=INFO, 13=WARN, 17=ERROR, 21=FATAL)。
SeverityText string 否 严重程度的文本表示(如 "ERROR"),用于兼容传统日志框架。
Body AnyValue 是 日志正文内容,可以是字符串、结构化对象或原始字节。
TraceId byte[16] 否 关联的 Trace ID,用于将日志绑定到分布式调用链。
SpanId byte[8] 否 关联的 Span ID,标识日志在调用链中的具体位置。
TraceFlags byte 否 Trace 的采样标记,用于指示该 Trace 是否被采样。
Resource Resource 是 标识日志来源的资源信息(服务名、主机名、容器ID等)。
InstrumentationScope InstrumentationScope 是 标识产生日志的库或框架(如 "my-logger", "otel-spring-boot")。
Attributes map[string]AnyValue 否 可扩展的键值对集合,用于携带任意上下文信息(如用户ID、请求路径、错误码)。

4.5 日志与 Trace 关联的代码示例

以下示例展示了如何在 Java 中使用 OTel API 创建一条关联到当前 Trace 的日志记录:

// 获取当前 Span
Span currentSpan = Span.current();
String traceId = currentSpan.getSpanContext().getTraceId();
String spanId = currentSpan.getSpanContext().getSpanId();

// 创建 LogRecord
LogRecordBuilder logBuilder = LoggerProvider.get("my-service")
    .loggerBuilder("my-logger")
    .build()
    .logRecordBuilder();

logBuilder
    .setTimestamp(Instant.now())
    .setSeverity(Severity.ERROR)
    .setBody("用户下单失败:库存不足")
    .setAttribute("user.id", "user_12345")
    .setAttribute("order.id", "order_67890")
    .setAttribute("error.code", "INSUFFICIENT_STOCK")
    // 关联 Trace
    .setTraceId(traceId)
    .setSpanId(spanId)
    .emit();
注意: 并非所有日志都需要关联 Trace。对于系统级日志(如服务器启动、配置加载),它们不属于任何请求上下文,此时 TraceId 和 SpanId 应留空。仅在业务请求处理过程中产生的日志才需要关联 Trace。

4.6 日志数据模型的核心价值

  • 结构化: 强制使用标准字段,消除日志格式碎片化,便于自动化处理和分析。
  • 可关联: 通过 TraceId/SpanId 与调用链关联,通过 Resource 与基础设施关联,打破数据孤岛。
  • 可扩展: Attributes 机制允许携带任意业务上下文,满足不同场景的定制需求。
  • 标准化: 无论使用哪种编程语言或日志框架,产生的 LogRecord 结构一致,后端可以统一处理。