🔍 登录 注册

OpenTelemetry 三信号关联 用 Trace 串起日志与指标

Z ZeroIsStart 2026-10-23 👁 0 ⏱ 6min

OpenTelemetry 三信号关联 用 Trace 串起日志与指标

指标告诉你"变慢了",日志告诉你"报了什么错",链路告诉你"慢在哪一跳"。三者分开时,一次故障要在三个系统之间来回跳,每次跳转都在丢上下文。OpenTelemetry 的核心价值不是采集,而是用同一套上下文把三者绑在一起。

一、关联的物理载体

能串起三信号的上下文只有三个字段:trace_id(16 字节,标识一次调用链)、span_id(8 字节,标识链路中的一个操作)、trace_flags(采样决策位)。

落地要求极其具体:日志里必须打出 trace_id 与 span_id。做不到这一点,其他一切手段都是装饰。

import "go.opentelemetry.io/otel/trace"

func Log(ctx context.Context, msg string, kv ...any) {
    sc := trace.SpanContextFromContext(ctx)
    base := []any{
        "trace_id", sc.TraceID().String(),
        "span_id", sc.SpanID().String(),
        "trace_sampled", sc.IsSampled(),
    }
    logger.InfoContext(ctx, msg, append(base, kv...)...)
}

字段名统一成 trace_id、span_id(Grafana 的 trace to logs 默认识别这两个)。不同服务混用 traceId、trace-id 会让跳转直接失效。

二、让指标能定位回 trace

指标是聚合数据,本身不带 trace_id,关联要靠 exemplar:Prometheus 允许在直方图 bucket 上挂样本点并附带 trace_id。

prometheus.MustRegister(prometheus.NewHistogramVec(prometheus.HistogramOpts{
    Name:    "http_request_duration_seconds",
    Buckets: prometheus.ExponentialBuckets(0.005, 2, 12),
}, []string{"route"}))
# prometheus.yml
storage:
  tsdb:
    out_of_order_time_window: 30m
# 启动参数: --enable-feature=exemplar-storage

效果是在 Grafana 的 P99 曲线上直接点击某个点,跳到那一次真实慢请求的 Trace。这是尾延迟排查中效率最高的一条通路。

三、Go 侧最小可信配置

func InitTracing(ctx context.Context, name, endpoint string) (func(), error) {
    res, err := otlptracegrpc.New(ctx,
        otlptracegrpc.WithEndpoint(endpoint),
        otlptracegrpc.WithInsecure(),
        otlptracegrpc.WithCompressor(gzip.Name))
    if err != nil { return nil, err }

    bsp := sdktrace.NewBatchSpanProcessor(res,
        sdktrace.WithMaxQueueSize(4096),
        sdktrace.WithMaxExportBatchSize(512),
        sdktrace.WithBatchTimeout(3*time.Second))

    tp := sdktrace.NewTracerProvider(
        sdktrace.WithSpanProcessor(bsp),
        sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL,
            semconv.ServiceName(name),
            attribute.String("deployment.environment", env))),
        sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1))))

    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))

    return func() { _ = tp.Shutdown(context.Background()) }, nil
}

四点必须注意:采样用 ParentBased(入口决定,下游一律跟随,否则链路断在半路);propagator 必须注册,否则跨服务不传播;BatchSpanProcessor 队列满会丢 span;Shutdown 要挂在进程退出路径上,否则最后一批 span 丢失。

四、下游插桩别漏

手写 span 只覆盖入口,DB 与 HTTP 调用要靠 instrumentation。

sqlDB, _ := db.DB()
otelsql.RegisterDBStatsMetrics(sqlDB, otelsql.WithAttributes(
    attribute.String("db.system", "mysql")))

client := otelhttp.NewClient(http.DefaultClient,
    otelhttp.WithSpanOptions(trace.WithSpanKind(trace.SpanKindClient)))

插桩三件套:入口 HTTP server、出口 HTTP client、DB。补齐 Redis 与 MQ 客户端后,一次请求的瀑布图才真正展开。

五、异步与消息的传播边界

异步任务和消息队列是链路断裂的高发区。规则很简单:生产端把 context 注入消息 header,消费端提取后续接。

// producer
carr := propagation.MapCarrier{}
otel.GetTextMapPropagator().Inject(ctx, carr)
for k, v := range carr { msg.Headers[k] = []byte(v) }

// consumer
ctx := otel.GetTextMapPropagator().Extract(context.Background(),
    propagation.MapCarrier(headerMap(msg.Headers)))
ctx, span := tracer.Start(ctx, "consume.order",
    trace.WithSpanKind(trace.SpanKindConsumer),
    trace.WithLinks(trace.Link{SpanContext: extractSC(msg.Headers)}))
defer span.End()

重试场景要用 SpanLink 而不是父子关系,因为每次重试逻辑上是独立调用。

六、后端拼装与验证清单

Tempo、Loki、Prometheus 三者关联靠 data source 的 derivedFields 与 trace to logs 跳转配置:

{ "name": "traceID", "matcherType": "label", "matcher": "trace_id",
  "internalLink": { "datasourceUid": "tempo", "urlDisplay": "查看链路" } }

配置完成后按三条验收:错误日志能一键跳到 Trace;P99 曲线上有可点击的 exemplar 点;Trace 的 span 能反向跳到对应实例该时段的日志。三个方向都通才算真正打通。

小结

落地顺序是固定的:统一日志里的 trace 字段 → 注册 propagator 与 ParentBased 采样 → 补齐 server/client/DB 插桩 → 用 exemplar 把指标点接回 Trace → 配双向跳转。Collector 与后端选型可以换,但"日志必须带 trace_id""采样必须 parent-based""消息必须注入 carrier"这三条是地基。三信号分开看只是三份数据,串起来才是可诊断的系统。

读完了?给作者一点支持

本文采用 CC BY 4.0 协议转载 · © ZeroIsStart

评论(0)

登录 后参与评论。

  • 还没有评论,来抢沙发。

🔗 相关文章