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。初始化通常放在应用启动阶段,全局只创建一次。
标准初始化流程包含三个步骤:
- 创建 Exporter:决定 Span 数据输出到哪里(控制台、Jaeger、OTLP Collector 等)。
- 创建 SpanProcessor:决定 Span 的处理策略(同步/异步、批处理)。
- 组装 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 创建的完整链路,帮助你建立全局视角:
2.5 常见陷阱与最佳实践
| 陷阱 | 后果 | 正确做法 |
|---|---|---|
忘记调用 span.End() |
Span 永远不会被导出,链路断裂 | 始终使用 defer span.End() |
| 每次请求都创建新的 TracerProvider | 内存泄漏,Exporter 连接数爆炸 | 全局单例,应用启动时初始化一次 |
| Span 名称使用动态字符串(如 SQL 全文) | 链路聚合失效,基数爆炸 | 使用固定模板,如 SELECT-users |
| 在 goroutine 中未传递上下文 | 父子 Span 关系丢失 | 显式传递 ctx 到 goroutine |
调试技巧: 开发阶段使用
stdouttrace 配合 WithPrettyPrint(),可以直接在控制台看到 JSON 格式的 Span 详情,快速验证属性、事件是否正确。