2. OpenTelemetry Go SDK 基础:SDK安装、TracerProvider初始化、基本Span创建

本章是实战课程的基石。你将掌握如何在 Go 项目中引入 OpenTelemetry SDK,完成核心组件 TracerProvider 的初始化,并创建出第一个可观测的 Span。这些基础操作将贯穿后续所有慢查询与连接池监控场景。

2.1 SDK 安装与依赖管理

OpenTelemetry Go 采用模块化架构,核心 API 与 SDK 分离。你只需安装必要的包,避免引入过多依赖。

包路径 作用 是否必须
go.opentelemetry.io/otel 核心 API:Tracer、Span、属性等接口定义 是
go.opentelemetry.io/otel/sdk SDK 实现:TracerProvider、SpanProcessor、Exporter 等 是
go.opentelemetry.io/otel/exporters/stdout/stdouttrace 标准输出导出器,用于调试和开发 推荐
go.opentelemetry.io/otel/semconv/v1.21.0 语义约定常量,统一属性命名 推荐
版本锁定建议: 生产项目中请使用 go get 固定主版本号,例如 go get go.opentelemetry.io/otel@v1.28.0,避免因 API 变更导致编译失败。

执行以下命令安装核心依赖:

go get go.opentelemetry.io/otel \
       go.opentelemetry.io/otel/sdk \
       go.opentelemetry.io/otel/exporters/stdout/stdouttrace \
       go.opentelemetry.io/otel/semconv/v1.21.0

2.2 TracerProvider 初始化

TracerProvider 是链路追踪的引擎。它负责管理 Tracer 实例、配置 SpanProcessor 和 Exporter。初始化通常放在应用启动阶段,全局只创建一次。

标准初始化流程包含三个步骤:

  1. 创建 Exporter:决定 Span 数据输出到哪里(控制台、Jaeger、OTLP Collector 等)。
  2. 创建 SpanProcessor:决定 Span 的处理策略(同步/异步、批处理)。
  3. 组装 TracerProvider:将 Processor 注册到 Provider,并设置为全局实例。
package main

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    "go.opentelemetry.io/otel/sdk/trace"
    "log"
    "os"
)

func initTracerProvider() (*trace.TracerProvider, error) {
    // 1. 创建标准输出 Exporter
    exporter, err := stdouttrace.New(
        stdouttrace.WithWriter(os.Stdout),
        stdouttrace.WithPrettyPrint(),
    )
    if err != nil {
        return nil, err
    }

    // 2. 创建 BatchSpanProcessor(异步批处理,推荐生产使用)
    processor := trace.NewBatchSpanProcessor(exporter)

    // 3. 创建 TracerProvider
    tp := trace.NewTracerProvider(
        trace.WithSpanProcessor(processor),
        trace.WithSampler(trace.AlwaysSample()), // 开发阶段全采样
    )

    // 4. 设置为全局 TracerProvider
    otel.SetTracerProvider(tp)

    return tp, nil
}

func main() {
    tp, err := initTracerProvider()
    if err != nil {
        log.Fatal(err)
    }
    defer func() {
        if err := tp.Shutdown(context.Background()); err != nil {
            log.Fatal(err)
        }
    }()

    // 应用逻辑...
}
资源泄漏防范: 务必在应用退出时调用 TracerProvider.Shutdown(),确保所有未导出的 Span 被刷新。建议使用 defer 或注册信号处理函数。

2.3 基本 Span 创建

Span 是链路追踪的最小工作单元。通过 Tracer 创建 Span,可以记录操作的开始、结束、属性和事件。

创建 Span 的标准模式:

func doWork(ctx context.Context) {
    // 从全局 Provider 获取 Tracer,命名通常为包名或服务名
    tracer := otel.Tracer("database-monitor")

    // 创建 Span,返回新的上下文
    ctx, span := tracer.Start(ctx, "query-database")
    defer span.End() // 必须延迟结束

    // 添加属性(键值对)
    span.SetAttributes(
        attribute.String("db.system", "postgresql"),
        attribute.String("db.statement", "SELECT * FROM users"),
        attribute.Int("db.rows_affected", 42),
    )

    // 添加事件(带时间戳的日志)
    span.AddEvent("connection-acquired")

    // 模拟业务逻辑
    // ...

    span.AddEvent("query-completed")
}
关键原则:
  • 每个 tracer.Start() 必须配对 span.End(),推荐使用 defer。
  • Span 名称应使用小写字母和连字符,如 http-request、db-query。
  • 属性值优先使用语义约定常量(semconv 包),保证跨服务一致性。

2.4 初始化与 Span 创建流程总览

下图展示了从 SDK 安装到 Span 创建的完整链路,帮助你建立全局视角:

OTel Go SDK 初始化与 Span 创建流程 1. SDK 安装 2. 创建 Exporter 3. 创建 Processor 4. 组装 TracerProvider 5. 获取 Tracer 6. 创建 Span 7. 添加属性/事件 ⚠ 关键顺序:SDK 安装 → Exporter → Processor → TracerProvider → Tracer → Span 每个 Span 必须调用 End(),否则数据丢失;TracerProvider 全局唯一,应用退出前必须 Shutdown()

2.5 常见陷阱与最佳实践

陷阱 后果 正确做法
忘记调用 span.End() Span 永远不会被导出,链路断裂 始终使用 defer span.End()
每次请求都创建新的 TracerProvider 内存泄漏,Exporter 连接数爆炸 全局单例,应用启动时初始化一次
Span 名称使用动态字符串(如 SQL 全文) 链路聚合失效,基数爆炸 使用固定模板,如 SELECT-users
在 goroutine 中未传递上下文 父子 Span 关系丢失 显式传递 ctx 到 goroutine
调试技巧: 开发阶段使用 stdouttrace 配合 WithPrettyPrint(),可以直接在控制台看到 JSON 格式的 Span 详情,快速验证属性、事件是否正确。