3、OpenTelemetry SDK 安装与初始化:otel-go SDK 安装、TracerProvider 配置、基本 Tracer 与 Span 创建

本章是链路追踪实战的起点。我们将完成 OpenTelemetry Go SDK 的安装、TracerProvider 的初始化配置,并创建第一个 Tracer 与 Span。掌握这些基础操作后,你就能在任何 Go 应用中埋入可观测的追踪点。

3.1 安装 otel-go SDK

OpenTelemetry Go 生态采用模块化设计,核心包与导出器分离。最小安装组合如下:

包路径 作用 是否必须
go.opentelemetry.io/otel 核心 API:Tracer、Span、Context 接口 是
go.opentelemetry.io/otel/sdk SDK 实现:TracerProvider、SpanProcessor、Exporter 基类 是
go.opentelemetry.io/otel/exporters/stdout/stdouttrace 控制台导出器,用于开发调试 推荐
go.opentelemetry.io/otel/semconv/v1.26.0 语义约定常量(服务名、HTTP 属性等) 推荐
版本锁定建议:生产项目请使用 go get 固定主版本,例如 go.opentelemetry.io/otel@v1.32.0,避免自动升级引入破坏性变更。

在项目根目录执行以下命令安装上述依赖:

go get go.opentelemetry.io/otel@v1.32.0
go get go.opentelemetry.io/otel/sdk@v1.32.0
go get go.opentelemetry.io/otel/exporters/stdout/stdouttrace@v1.32.0
go get go.opentelemetry.io/otel/semconv/v1.26.0

安装完成后,go.mod 中应出现对应版本记录。你可以运行 go mod tidy 清理未使用的依赖。

3.2 TracerProvider 配置

TracerProvider 是追踪系统的入口,负责创建 Tracer 并管理 Span 的生命周期。一个典型的初始化流程包含三个步骤:

  1. 创建 Exporter:决定 Span 数据输出到哪里(控制台、Jaeger、OTLP 等)。
  2. 创建 SpanProcessor:决定 Span 何时被导出(同步/异步、批处理)。
  3. 组装 TracerProvider:将 Processor 和 Resource 注入 Provider。

下面是一个完整的初始化代码,使用控制台导出器:

package main

import (
    "context"
    "log"
    "os"
    "os/signal"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

func initTracerProvider() (*sdktrace.TracerProvider, error) {
    // 1. 创建控制台 Exporter
    exporter, err := stdouttrace.New(
        stdouttrace.WithPrettyPrint(),
    )
    if err != nil {
        return nil, err
    }

    // 2. 创建 BatchSpanProcessor(异步批量导出)
    processor := sdktrace.NewBatchSpanProcessor(exporter)

    // 3. 定义 Resource(服务标识)
    res, err := resource.New(context.Background(),
        resource.WithAttributes(
            semconv.ServiceNameKey.String("my-go-service"),
            attribute.String("environment", "development"),
        ),
    )
    if err != nil {
        return nil, err
    }

    // 4. 组装 TracerProvider
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithSpanProcessor(processor),
        sdktrace.WithResource(res),
        sdktrace.WithSampler(sdktrace.AlwaysSample()),
    )

    // 5. 设置为全局 Provider
    otel.SetTracerProvider(tp)
    return tp, nil
}

func main() {
    tp, err := initTracerProvider()
    if err != nil {
        log.Fatal(err)
    }

    // 优雅关闭:确保所有 Span 在进程退出前被导出
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()

    <-ctx.Done()
    if err := tp.Shutdown(context.Background()); err != nil {
        log.Fatal(err)
    }
}
关键配置说明:
  • WithSampler(AlwaysSample()):开发阶段采样率设为 100%;生产环境建议改用 TraceIDRatioBased(0.1) 降低开销。
  • WithResource:必须设置 service.name,否则后端无法区分服务来源。
  • Shutdown:必须在进程退出前调用,否则可能丢失未导出的 Span。

3.3 基本 Tracer 与 Span 创建

有了全局 TracerProvider 后,我们可以通过 otel.Tracer(name) 获取 Tracer 实例,然后创建 Span。下面演示一个模拟数据库查询的追踪场景:

func businessLogic(ctx context.Context) {
    // 从全局 Provider 获取 Tracer
    tracer := otel.Tracer("com.example.business")

    // 创建根 Span
    ctx, span := tracer.Start(ctx, "handle-request")
    defer span.End()

    // 给 Span 添加属性
    span.SetAttributes(
        attribute.String("request.id", "req-12345"),
        attribute.Int("priority", 1),
    )

    // 模拟子操作:数据库查询
    dbQuery(ctx, tracer)

    // 记录事件
    span.AddEvent("request.processed",
        attribute.String("result", "success"),
    )
}

func dbQuery(ctx context.Context, tracer trace.Tracer) {
    // 创建子 Span,自动继承父 Span 的 TraceID
    ctx, span := tracer.Start(ctx, "db-query")
    defer span.End()

    span.SetAttributes(
        attribute.String("db.system", "postgresql"),
        attribute.String("db.statement", "SELECT * FROM users"),
    )

    // 模拟耗时
    // time.Sleep(50 * time.Millisecond)
}
常见陷阱:
  • 忘记调用 span.End() 会导致 Span 永远不被导出,造成内存泄漏。
  • 子 Span 必须使用父 Span 传递下来的 ctx,否则会丢失父子关系,变成独立 Trace。
  • tracer.Start 第二个参数是 Span 名称,建议使用 package.function 格式,便于检索。

3.4 初始化与调用流程总览

下图展示了从 SDK 安装到 Span 导出的完整数据流:

OpenTelemetry Go SDK 初始化与追踪流程 otel-go SDK 初始化与 Span 导出流程 应用代码 TracerProvider SpanProcessor Exporter Jaeger / OTLP otel.Tracer() Span 入队 批量导出 gRPC / HTTP 说明: ① 应用通过 otel.Tracer() 获取 Tracer 并创建 Span;② Span 被 SpanProcessor 异步批量处理; ③ Exporter 将 Span 序列化后发送到 Jaeger/OTLP 等后端存储。

3.5 配置参数速查表

下表汇总了 TracerProvider 初始化时最常用的配置项及其推荐值:

配置项 类型 推荐值 / 说明
WithSampler sdktrace.Sampler 开发:AlwaysSample();生产:TraceIDRatioBased(0.1)
WithResource *resource.Resource 必须包含 service.name,可选 service.version、deployment.environment
WithSpanProcessor sdktrace.SpanProcessor 推荐 NewBatchSpanProcessor,吞吐量优于同步处理器
WithIDGenerator sdktrace.IDGenerator 默认随机生成;可替换为自定义生成器(如兼容 AWS X-Ray 格式)
WithRawSpanLimits sdktrace.SpanLimits 默认限制:属性 128 个、事件 128 个、链接 32 个;按需调整

3.6 验证初始化是否成功

完成上述代码后,运行程序并发送一个请求。控制台应输出类似下面的 JSON 结构:

{
  "Name": "handle-request",
  "SpanContext": {
    "TraceID": "a1b2c3d4e5f6...",
    "SpanID": "12345678...",
    "TraceFlags": "01"
  },
  "Parent": "",
  "StartTime": "2025-03-20T10:00:00.000Z",
  "EndTime": "2025-03-20T10:00:00.050Z",
  "Attributes": [
    {"Key": "request.id", "Value": {"Type": "STRING", "Value": "req-12345"}}
  ],
  "Events": [
    {
      "Name": "request.processed",
      "Attributes": [{"Key": "result", "Value": {"Type": "STRING", "Value": "success"}}]
    }
  ]
}
快速验证技巧:如果控制台没有输出,请检查:① 是否调用了 otel.SetTracerProvider(tp);② 进程退出前是否执行了 tp.Shutdown();③ 采样器是否设置为 AlwaysSample()。

至此,你已经完成了 OpenTelemetry Go SDK 的安装、TracerProvider 的配置,并成功创建了第一个 Tracer 与 Span。下一阶段我们将深入 Span 的属性、事件与链路传播机制。