mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3458 字
10 分钟
信号关联:连接日志/指标/追踪/性能
2025-10-04

三大信号如果各自为战,可观测性就退化为”三个监控”。信号关联(Signal Correlation)是可观测性的灵魂——它让指标发现异常后直接跳转到追踪定位瓶颈,让追踪找到慢 Span 后直接查看关联日志,让日志发现错误后直接查看性能热点。

没有关联,排障需要你在 Grafana、Jaeger、Kibana 之间手动跳转和搜索;有关联,排障变成一条流畅的点击链路——从发现到定位到理解,几分钟内完成。

一、关联的本质#

1.1 关联 ID 是桥梁#

信号关联的核心是关联 ID——在多个信号中共享的身份标识符:

graph TB METRIC[" 指标<br/>Exemplar: trace_id=abc123"] TRACE[" 追踪<br/>trace_id=abc123<br/>span_id=span1"] LOG[" 日志<br/>trace_id=abc123<br/>span_id=span1"] PROFILE[" 性能分析<br/>profile_id=prof1<br/>span_id=span1"] METRIC -.->|"Exemplar"| TRACE LOG -.->|"TraceID"| TRACE PROFILE -.->|"ProfileID"| TRACE style METRIC fill:#e3f2fd,stroke:#1565c0 style TRACE fill:#e8f5e9,stroke:#2e7d32 style LOG fill:#fff3e0,stroke:#e65100 style PROFILE fill:#fce4ec,stroke:#880e4f
关联 ID源信号目标信号说明
trace_id指标(Exemplar)追踪从指标跳转到慢请求追踪
trace_id日志追踪从日志跳转到请求追踪
span_id日志追踪 Span从日志跳转到具体 Span
profile_id追踪 Span性能分析从 Span 跳转到火焰图

1.2 关联体验#

没有关联的排障

  1. 看到指标异常 → 手动复制时间范围
  2. 打开 Jaeger → 手动搜索服务名和时间范围
  3. 找到慢追踪 → 手动复制 TraceID
  4. 打开 Kibana → 手动搜索 TraceID
  5. 找到错误日志 → 手动分析

有关联的排障

  1. 看到指标异常 → 点击 Exemplar → 自动跳转到追踪
  2. 在追踪中点击 Span → 自动查看关联日志
  3. 在日志中看到性能问题 → 自动查看火焰图

1.3 关联的层次模型#

信号关联不是简单的”跳转链接”,而是一个分层体系:

graph TB subgraph L1["第一层:身份关联"] ID["TraceID / SpanID / ProfileID<br/>在多个信号中共享同一标识"] end subgraph L2["第二层:上下文关联"] CTX["时间范围 + 服务名 + 环境标签<br/>缩小关联搜索的范围"] end subgraph L3["第三层:语义关联"] SEM["指标名 → 追踪操作名 → 日志错误码<br/>信号之间的语义映射"] end L1 --> L2 --> L3 style L1 fill:#e3f2fd,stroke:#1565c0 style L2 fill:#e8f5e9,stroke:#2e7d32 style L3 fill:#fff3e0,stroke:#e65100
层次依赖示例
身份关联TraceID 传播日志中的 trace_id 匹配追踪中的 traceID
上下文关联标签一致性指标中的 service=order 匹配追踪中的 service.name=order
语义关联命名约定统一指标 http_server_request_duration_seconds 对应追踪 HTTP GET /api/orders

二、Exemplar:指标直通追踪#

2.1 什么是 Exemplar?#

Exemplar 是 Prometheus 2.26+ 引入的特性,允许在 Histogram 桶中附加一个关联引用(通常是 TraceID):

# 普通 Histogram 桶
http_request_duration_bucket{le="0.5"} 80
# 带 Exemplar 的 Histogram 桶
http_request_duration_bucket{le="0.5"} 80 # {trace_id="abc123"} 0.45

Exemplar 告诉你:“这个桶中有一个耗时 0.45s 的请求,它的 TraceID 是 abc123”。

2.2 Exemplar 的存储与查询机制#

Exemplar 的实现比看起来更精巧。它不是给每个样本都附加 TraceID,而是每个 Histogram 桶只保留最近一个 Exemplar

sequenceDiagram participant App as 应用 participant SDK as OTel SDK participant Prom as Prometheus participant Grafana as Grafana App->>SDK: 请求1完成 (120ms) SDK->>SDK: bucket{le="0.5"} +1<br/>exemplar: trace_id=aaa App->>SDK: 请求2完成 (350ms) SDK->>SDK: bucket{le="0.5"} +1<br/>exemplar: trace_id=bbb (覆盖aaa) Note right of SDK: 每个桶只保留最新 Exemplar App->>SDK: 请求3完成 (1.2s) SDK->>SDK: bucket{le="2.5"} +1<br/>exemplar: trace_id=ccc SDK->>Prom: 暴露指标 + Exemplar Prom->>Grafana: 查询结果包含 Exemplar Note right of Grafana: 用户点击 Exemplar<br/>跳转到追踪 bbb 或 ccc

这个设计是有意为之的:如果每个样本都保留 Exemplar,存储量会翻倍以上。只保留最新的 Exemplar 是一个合理的折中——它让你总是能跳转到最近的慢请求追踪。

Note

Exemplar 的采样策略因 SDK 而异。OTel Go SDK 默认在 Histogram 记录时附加当前 SpanContext;OTel Java SDK 则支持配置 Exemplar 采样策略(如只保留超过 P99 阈值的 Exemplar)。选择哪种策略取决于你的存储成本和关联需求。

2.3 Exemplar 的配置细节#

Exemplar 在 Prometheus 侧也需要启用存储支持:

# Prometheus 启用 Exemplar 存储
storage:
tsdb:
path: /prometheus
# Exemplar 最大存储量,默认 0(不存储)
# 建议设置为 100000(10 万条),约占 100MB 内存
max_exemplars: 100000
# 也可以通过命令行参数
# --storage.tsdb.max-exemplars=100000
参数默认值建议值说明
max_exemplars0(禁用)100000Exemplar 环形缓冲区大小
内存开销~1KB/条100000 条约 100MB
淘汰策略FIFO超出 max_exemplars 后淘汰最旧的

2.4 OTel SDK 配置 Exemplar#

// Go: OTel SDK 自动在 Histogram 中附加 Exemplar
// 无需额外配置,OTel SDK 默认将当前 SpanContext 作为 Exemplar
var requestDuration metric.Float64Histogram
func handleRequest(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "handleRequest")
defer span.End()
start := time.Now()
// 处理请求...
duration := time.Since(start).Seconds()
// Record 会自动附加当前 SpanContext 作为 Exemplar
requestDuration.Record(ctx, duration,
metric.WithAttributes(
semconv.HTTPMethodKey.String(r.Method),
semconv.HTTPRouteKey.String("/api/v1/orders"),
),
)
}
application.yml
// Java: OTel SDK 配置 Exemplar
otel:
exemplar:
# 只保留超过阈值的 Exemplar(减少存储开销)
filter:
type: trace_based
# 采样率:只保留被采样的追踪的 Exemplar
metrics:
exporter: otlp
histogram:
# 使用显式桶边界,确保 Exemplar 落入正确的桶
explicit-buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
# Python: OTel SDK 自动附加 Exemplar
from opentelemetry import metrics, trace
from opentelemetry.sdk.metrics import MeterProvider
# 默认行为:Histogram 记录时自动附加当前 SpanContext
meter = metrics.get_meter("order-service")
request_duration = meter.create_histogram(
name="http.server.request.duration",
unit="s",
description="HTTP 请求延迟分布",
)
def handle_request(request):
tracer = trace.get_tracer("order-service")
with tracer.start_as_current_span("handle_request") as span:
# 处理请求...
request_duration.record(0.45, {"method": "GET", "route": "/api/orders"})
# Exemplar 自动附加当前 span 的 trace_id

2.5 Grafana 中的 Exemplar 体验#

在 Grafana 面板中,Exemplar 以小圆点显示在时间序列上:

  1. 在 P99 延迟面板中,点击 Exemplar 小圆点
  2. 自动跳转到 Tempo,查询对应的 TraceID
  3. 在追踪视图中定位慢 Span
  4. 从 Span 跳转到关联日志

Exemplar 小圆点的颜色和大小也有含义——越大的点表示越慢的请求,颜色越红表示越接近 SLO 违规。这让你可以快速识别”最值得调查”的慢请求。

三、TraceID 关联:日志与追踪#

3.1 注入 TraceID 到日志#

// Go: 自动注入 TraceID 到 slog
type OTelHandler struct {
inner slog.Handler
}
func (h OTelHandler) Handle(ctx context.Context, r slog.Record) error {
span := trace.SpanFromContext(ctx)
if span.SpanContext().IsValid() {
r.AddAttrs(
slog.String("trace_id", span.SpanContext().TraceID().String()),
slog.String("span_id", span.SpanContext().SpanID().String()),
)
}
return h.inner.Handle(ctx, r)
}

3.2 TraceID 注入的多语言实现#

不同语言的日志框架对 TraceID 注入的支持方式不同。以下是四种主流语言的实现模式:

// Java: 使用 MDC (Mapped Diagnostic Context) 注入 TraceID
// OTel Java Agent 自动将 trace_id/span_id 注入 MDC
// logback-spring.xml 配置
<configuration>
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<!-- 自动从 MDC 提取 trace_id -->
<includeMdcKeyName>trace_id</includeMdcKeyName>
<includeMdcKeyName>span_id</includeMdcKeyName>
<includeMdcKeyName>trace_flags</includeMdcKeyName>
</encoder>
</appender>
</configuration>
# Python: 使用 structlog + OTel 注入
import structlog
from opentelemetry import trace
def add_trace_id(logger, method_name, event_dict):
"""自定义 processor:注入 trace_id 和 span_id"""
span = trace.get_current_span()
if span.is_recording():
ctx = span.get_span_context()
event_dict["trace_id"] = format(ctx.trace_id, "032x")
event_dict["span_id"] = format(ctx.span_id, "016x")
return event_dict
structlog.configure(
processors=[
add_trace_id, # 注入 TraceID
structlog.processors.JSONRenderer(), # JSON 输出
],
)
// Node.js: 使用 OTel API 注入 TraceID
import { trace, context } from '@opentelemetry/api';
import pino from 'pino';
const logger = pino({
formatters: {
log(object) {
const span = trace.getSpan(context.active());
if (span) {
const { traceId, spanId } = span.spanContext();
object.trace_id = traceId;
object.span_id = spanId;
}
return object;
},
},
});
语言日志框架注入方式是否需要手动代码
Goslog/zap自定义 Handler是(~20 行)
JavaLogback/Log4jMDC + OTel Agent否(Agent 自动注入)
Pythonstructlog自定义 Processor是(~10 行)
Node.jsPino/Winston自定义 Formatter是(~10 行)
RusttracingOTel Layer是(~15 行)
Tip

Java 是唯一可以零代码注入 TraceID 的语言——OTel Java Agent 通过字节码增强自动将 trace_id 注入 MDC。其他语言都需要少量手动代码,但代码量通常不超过 20 行。建议将注入逻辑封装为项目内部的日志库,避免每个服务重复实现。

3.3 Grafana 中的日志-追踪关联#

在 Grafana Explore 中,日志和追踪可以无缝关联:

  1. 在 Loki 查询日志:{service="order-service"} | json | level="ERROR"
  2. 日志行中显示 TraceID 链接
  3. 点击 TraceID → 自动跳转到 Tempo 查询追踪
  4. 在追踪视图中,点击 Span → 自动过滤该 Span 的日志

3.4 配置数据源关联#

# Grafana 数据源关联配置
apiVersion: 1
datasources:
- name: Tempo
type: tempo
uid: tempo
jsonData:
tracesToMetrics:
datasourceUid: prometheus
tags:
- key: service
value: service
tracesToLogs:
datasourceUid: loki
filterByTraceID: true
filterBySpanID: true
tags:
- key: service
value: service
serviceMap:
datasourceUid: prometheus
- name: Loki
type: loki
uid: loki
jsonData:
derivedFields:
- name: TraceID
matcherRegex: '"trace_id":"(\w+)"'
url: '$${__value.raw}'
datasourceUid: tempo
- name: Prometheus
type: prometheus
uid: prometheus
jsonData:
exemplarTraceIdDestinations:
- name: trace_id
datasourceUid: tempo
urlDisplayLabel: 'View Trace'

3.5 数据源关联的完整拓扑#

上面只展示了最基础的关联配置。在一个完整的可观测性平台中,数据源之间的关联形成一个网状拓扑:

graph LR PROM["Prometheus<br/>指标"] -->|"Exemplar"| TEMPO["Tempo<br/>追踪"] TEMPO -->|"TraceID"| LOKI["Loki<br/>日志"] LOKI -->|"derivedField"| TEMPO TEMPO -->|"ProfileID"| PYRO["Pyroscope<br/>性能"] PROM -->|"ServiceMap"| TEMPO TEMPO -->|"tracesToMetrics"| PROM style PROM fill:#e3f2fd,stroke:#1565c0 style TEMPO fill:#e8f5e9,stroke:#2e7d32 style LOKI fill:#fff3e0,stroke:#e65100 style PYRO fill:#fce4ec,stroke:#880e4f
关联方向配置位置关键字段
Prometheus → TempoPrometheus exemplarTraceIdDestinationsdatasourceUid: tempo
Tempo → LokiTempo tracesToLogsfilterByTraceID: true
Loki → TempoLoki derivedFieldsmatcherRegex + datasourceUid
Tempo → PrometheusTempo tracesToMetricsdatasourceUid: prometheus
Tempo → PyroscopeTempo tracesToProfilesprofileType: cpu

四、ProfileID 关联:追踪与性能分析#

4.1 从追踪跳转到火焰图#

OTel 正在标准化 ProfileID 关联——在 Span 中附加 profile_id 属性,让追踪可以跳转到对应时间点的火焰图:

{
"span_id": "span1",
"attributes": {
"profile_id": "prof_abc123",
"profile.type": "cpu"
}
}

4.2 ProfileID 关联的配置#

在 Grafana 中配置追踪到性能分析的关联:

# Tempo 数据源配置:追踪 → 性能分析
- name: Tempo
type: tempo
uid: tempo
jsonData:
tracesToProfiles:
datasourceUid: pyroscope
# Profile 类型:cpu、memory、goroutine 等
profileType: process_cpu:cpu:nanoseconds:cpu:nanoseconds
# 自定义标签映射
customQueryParameters:
service: service.name
# 时间范围偏移(Profile 采集间隔可能不同)
spanEndTimeShift: '-30s'
spanStartTimeShift: '30s'
# Pyroscope 数据源配置
- name: Pyroscope
type: grafana-pyroscope-datasource
uid: pyroscope
url: http://pyroscope:4040

4.3 ProfileID 关联的技术原理#

ProfileID 关联比 TraceID 关联更复杂,因为性能分析数据是周期性采集的(通常每 10-60 秒采集一次),而不是请求驱动的。这意味着:

对比维度TraceID 关联ProfileID 关联
数据产生请求驱动(每个请求一条追踪)周期驱动(每 10-60s 一次采样)
关联精度精确到请求近似到时间窗口
关联方式精确匹配 TraceID时间范围 + 标签匹配 + ProfileID
存储开销追踪数据本身Profile 数据独立存储

因此,ProfileID 关联实际上有两种实现方式:

  1. 精确关联:在 Span 中附加 profile_id,指向该 Span 执行期间采集的 Profile。需要 OTel SDK 和 Pyroscope Agent 协同工作。
  2. 近似关联:根据 Span 的时间范围和标签(service name),在 Pyroscope 中查询对应时间窗口的 Profile。不需要 ProfileID,但精度较低。
Note

精确关联(ProfileID)是 OTel Profiles 规范的一部分,目前处于 Beta 阶段。Grafana Tempo 2.4+ 和 Pyroscope 1.3+ 已支持此功能。如果你使用的是旧版本,可以先用近似关联过渡。

4.4 Grafana 中的关联体验#

  1. 在追踪视图中,Span 旁边显示火焰图图标
  2. 点击图标 → 自动跳转到 Pyroscope
  3. 显示该 Span 执行期间的火焰图
  4. 可以看到 Span 耗时的函数级分解

五、端到端关联排障实战#

5.1 场景:P99 延迟飙升#

sequenceDiagram participant M as 指标面板 participant T as 追踪详情 participant L as 日志详情 participant P as 火焰图 Note over M: P99 延迟从 200ms 飙升到 2s M->>T: 点击 Exemplar<br/>跳转到慢请求追踪 Note over T: 定位到 db.query Span<br/>耗时 1.8s T->>L: 点击 Span<br/>查看关联日志 Note over L: 发现错误日志<br/>connection pool exhausted L->>P: 点击 ProfileID<br/>查看火焰图 Note over P: 发现热点函数<br/>DB.waitConnection() Note over P: 根因: 连接池配置过小

5.2 完整排障演练:订单服务延迟飙升#

走一遍完整的端到端排障流程,从告警触发到根因定位:

背景:订单服务的 P99 延迟在 10 分钟内从 200ms 飙升到 2s,SLO 燃烧率告警触发。

第一步:指标面板确认异常

在 Grafana 的 SLO 面板中,看到可用性 SLO 的错误预算正在快速燃烧。切换到延迟面板,确认 P99 飙升但 P50 正常——典型的尾部延迟问题。

第二步:Exemplar 跳转到追踪

点击 P99 曲线上的红色 Exemplar 小圆点,Grafana 自动跳转到 Tempo,展示 TraceID 为 7f3b2a1c4d5e6f7890abcdef12345678 的追踪。

第三步:追踪定位瓶颈 Span

追踪瀑布图显示:

  • HTTP GET /api/v1/orders — 总耗时 1.95s
    • order-service.processOrder — 1.90s
      • db.query(SELECT * FROM orders) — 1.82s ← 瓶颈!
      • cache.get(order:12345) — 0.003s

第四步:Span 关联日志

点击 db.query Span,Grafana 自动过滤该 Span 的日志:

{
"timestamp": "2026-06-27T02:15:32.456Z",
"level": "WARN",
"service": "order-service",
"trace_id": "7f3b2a1c4d5e6f7890abcdef12345678",
"span_id": "a1b2c3d4e5f67890",
"message": "connection pool exhausted, waiting for available connection",
"wait_time_ms": 1820,
"pool_size": 50,
"active_connections": 50,
"pending_requests": 30
}

第五步:ProfileID 关联火焰图

点击 Span 旁的火焰图图标,跳转到 Pyroscope。火焰图显示 DB.waitConnection() 占了 90% 的 CPU 时间——这不是 CPU 热点,而是等待热点:线程在等待连接池分配连接。

第六步:确认根因并修复

根因:数据库连接池 max=50,但并发请求数已增长到 80。修复方案:将连接池 max 从 50 增加到 100。

// 修复前
db.SetMaxOpenConns(50)
// 修复后
db.SetMaxOpenConns(100)
db.SetMaxIdleConns(25) // 同时增加空闲连接数
db.SetConnMaxLifetime(5 * time.Minute) // 连接最大存活时间

第七步:验证修复效果

部署修复后,P99 延迟在 2 分钟内恢复到 200ms 以下,SLO 燃烧率恢复正常。

5.3 关联配置清单#

关联配置项验证方式
指标 → 追踪Prometheus Exemplar → Tempo点击 Exemplar 跳转
追踪 → 日志Tempo → Loki (TraceID)追踪中查看关联日志
日志 → 追踪Loki derivedFields → Tempo日志中点击 TraceID
追踪 → 指标Tempo → Prometheus (RED)追踪中查看服务指标
追踪 → 性能Tempo → Pyroscope (ProfileID)Span 中查看火焰图

六、关联的反模式#

6.1 常见反模式#

反模式症状解决方案
日志无 TraceID无法从日志跳转到追踪自动注入 TraceID
Exemplar 未启用指标无法跳转到追踪OTel SDK 默认启用
数据源未关联Grafana 中无法跳转配置 derivedFields
追踪链路断裂跨服务追踪不完整检查 W3C TraceContext 传播
时间不对齐关联跳转后看不到数据确保时钟同步
标签不一致关联跳转后过滤条件不匹配统一标签命名约定
Exemplar 存储未启用Prometheus 不存储 Exemplar配置 max_exemplars

6.2 反模式深入分析#

反模式 1:日志有 TraceID 但格式不统一

这是最常见的”看起来有关联但实际不好用”的问题。不同服务的日志格式不一致:

// 服务 A:下划线格式
{"trace_id": "abc123", "span_id": "def456"}
// 服务 B:驼峰格式
{"traceId": "abc123", "spanId": "def456"}
// 服务 C:嵌套格式
{"context": {"trace": {"id": "abc123"}, "span": {"id": "def456"}}}

Grafana 的 derivedFields 只能匹配一种正则表达式。如果格式不统一,部分服务的日志就无法跳转。

解决方案:在 OTel Collector 中统一日志格式:

# OTel Collector: 统一日志中的 TraceID 格式
processors:
transform/log_format:
error_mode: ignore
log_statements:
- context: log
statements:
# 将所有格式统一为 trace_id(下划线)
- set(attributes["trace_id"], attributes["traceId"]) where attributes["traceId"] != nil
- set(attributes["span_id"], attributes["spanId"]) where attributes["spanId"] != nil

反模式 2:Exemplar 只在部分桶中出现

Exemplar 只在 Histogram 桶中出现,Counter 和 Gauge 不支持 Exemplar。如果你用 Gauge 记录延迟(而不是 Histogram),就无法使用 Exemplar 跳转。

// 错误:用 Gauge 记录延迟,无法使用 Exemplar
latencyGauge.Set(ctx, duration)
// 正确:用 Histogram 记录延迟,自动附加 Exemplar
latencyHistogram.Record(ctx, duration)

反模式 3:追踪采样导致关联断裂

如果追踪采样率是 10%,那么 90% 的请求没有追踪数据。Exemplar 跳转到 Tempo 后可能找不到对应的 TraceID——因为那条追踪被采样丢弃了。

解决方案:使用尾部采样(Tail-Based Sampling),确保错误和慢请求的追踪总是被保留:

# OTel Collector: 尾部采样配置
processors:
tail_sampling:
decision_wait: 10s
num_traces: 100000
policies:
# 错误追踪总是保留
- name: errors
type: status_code
status_code:
status_codes:
- ERROR
# 慢请求追踪总是保留
- name: slow-requests
type: latency
latency:
threshold_ms: 1000
# 其他追踪 10% 采样
- name: default
type: probabilistic
probabilistic:
sampling_percentage: 10
Warning

信号关联不是自动的——它需要你在架构层面设计。最关键的一步是在所有日志中注入 TraceID。如果日志里没有 TraceID,信号关联就无从谈起。此外,确保日志格式统一、追踪采样策略与 Exemplar 兼容,否则关联体验会大打折扣。

七、关联的验证与测试#

7.1 自动化关联测试#

手动验证关联链路很繁琐——你需要制造请求、查看指标、点击 Exemplar、检查跳转。更好的方式是编写自动化测试:

// Go: 自动化关联测试
func TestTraceIDCorrelation(t *testing.T) {
// 1. 发送一个带追踪的请求
ctx, span := tracer.Start(context.Background(), "test-request")
traceID := span.SpanContext().TraceID().String()
// 2. 记录指标(带 Exemplar)
requestDuration.Record(ctx, 0.5)
// 3. 记录日志(带 TraceID)
slog.InfoContext(ctx, "test log", "key", "value")
// 4. 验证 Tempo 中有该 TraceID
trace, err := tempoClient.GetTrace(traceID)
require.NoError(t, err)
require.NotNil(t, trace)
// 5. 验证 Loki 中有该 TraceID 的日志
logs, err := lokiClient.Query(`{service="test"} |= "` + traceID + `"`)
require.NoError(t, err)
require.NotEmpty(t, logs)
span.End()
}

7.2 关联健康检查面板#

在 Grafana 中创建一个关联健康检查面板,持续监控关联链路的可用性:

检查项PromQL/LogQL健康标准
Exemplar 存储使用率prometheus_tsdb_exemplar_storage_series> 0
追踪采样率traces_sampling_rate> 0(与配置一致)
日志 TraceID 覆盖率count(logs_with_trace_id) / count(all_logs)> 95%
关联跳转成功率自定义指标> 99%

八、动手实践#

8.1 配置 Grafana 数据源关联#

# 更新 Grafana 数据源配置
docker compose restart grafana
# 打开 Grafana
open http://localhost:3000/explore

8.2 验证关联链路#

# 1. 在 Prometheus 面板中点击 Exemplar
# 2. 验证跳转到 Tempo 追踪
# 3. 在追踪中点击 Span
# 4. 验证查看关联日志
# 5. 在日志中点击 TraceID
# 6. 验证跳转回追踪

8.3 制造关联数据#

# 发送一个带追踪的请求,验证完整的关联链路
curl -X POST http://localhost:8080/api/v1/orders \
-H "Content-Type: application/json" \
-H "traceparent: 00-7f3b2a1c4d5e6f7890abcdef12345678-a1b2c3d4e5f67890-01" \
-d '{"item": "test-order"}'
# 在 Tempo 中查询该 TraceID
curl http://localhost:3200/api/traces/7f3b2a1c4d5e6f7890abcdef12345678
# 在 Loki 中查询该 TraceID 的日志
curl "http://localhost:3100/loki/api/v1/query_range?query={service=\"order-service\"}+%7C%3D+%227f3b2a1c4d5e6f7890abcdef12345678%22"

8.4 验证清单#

检查项验证方式预期结果
Exemplar 可点击指标面板中显示小圆点
指标→追踪点击 Exemplar跳转到 Tempo
追踪→日志点击 Span显示关联日志
日志→追踪点击 TraceID跳转到 Tempo
追踪→指标服务详情显示 RED 指标
追踪→性能Span 火焰图图标跳转到 Pyroscope

九、本章小结#

上一章建立了可观测性存储后端的认知框架。 用了整章的篇幅拆解可观测性的灵魂——信号关联。

主题核心要点关键词
关联 IDTraceID、SpanID、ProfileID 是连接四大信号的桥梁。关联 ID
Exemplar在 Histogram 桶中附加 TraceID,让指标直通追踪。每个桶只保留最新 Exemplar,是存储和关联的合理折中。Exemplar
日志-追踪关联在日志中注入 TraceID,在 Grafana 中配置 derivedFields。多语言实现方式不同,但代码量通常不超过 20 行。日志-追踪关联
追踪-性能关联在 Span 中附加 ProfileID,让追踪跳转到火焰图。精确关联需要 OTel Profiles 规范支持。追踪-性能关联
端到端体验从指标发现异常,到追踪定位瓶颈,到日志确认根因,到性能分析深入——一条流畅的点击链路。端到端体验
反模式日志无 TraceID、格式不统一、Exemplar 存储未启用、追踪采样导致关联断裂——这些是关联失败的最常见原因。反模式

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

信号关联:连接日志/指标/追踪/性能
https://blog.souloss.com/posts/observability/signal-correlation/
作者
Souloss
发布于
2025-10-04
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时