OpenTelemetry Go SDK¶
OpenTelemetry Go SDK 通过 API、SDK 和框架插桩库采集 Go 应用的遥测数据。与 Java Agent 等运行时自动插桩方案不同,Go 应用通常需要在代码中初始化 SDK,并使用相应的插桩库包装 Web 框架、HTTP 客户端、数据库或消息队列。
本文使用 DataKit 的 OpenTelemetry 采集器接收 OTLP 数据并转发到观测云:
本文示例通过 OTLP/HTTP + Protobuf 上报 Trace 和 Metric。OpenTelemetry Go 的 Trace、Metric 已达到稳定状态;Log SDK 的成熟度和生态支持仍可能变化,生产环境接入前应核对当前官方状态。
前置条件¶
- Go 1.23 或更高版本;
- 已安装 DataKit,且 DataKit 已连接到目标观测云工作空间;
- Go 应用到 DataKit 的网络可达:OTLP/HTTP 使用 DataKit HTTP 端口
9529,OTLP/gRPC 默认使用4317; - 已确认应用所使用的框架或组件存在对应的 OpenTelemetry Go 插桩库,或计划通过 OpenTelemetry API 手工创建 Span 和 Metric。
一、开启 OpenTelemetry 采集器¶
进入 DataKit 安装目录下的 conf.d/opentelemetry。如果尚未创建采集器配置,复制示例文件:
确认 opentelemetry.conf 至少包含以下接收配置:
[[inputs.opentelemetry]]
# 如需在观测云中将自定义属性作为标签保留,请在此加入白名单。
# 属性名中的点会被转换为下划线,例如 team.name -> team_name。
customer_tags = ["team", "project"]
[inputs.opentelemetry.http]
http_status_ok = 200
trace_api = "/otel/v1/traces"
metric_api = "/otel/v1/metrics"
logs_api = "/otel/v1/logs"
[inputs.opentelemetry.grpc]
addr = "127.0.0.1:4317"
max_payload = 16777216
以上配置开启以下接收地址:
| 协议 | 数据类型 | DataKit 接收地址 |
|---|---|---|
| OTLP/HTTP + Protobuf | Trace | http://<DataKit-IP>:9529/otel/v1/traces |
| OTLP/HTTP + Protobuf | Metric | http://<DataKit-IP>:9529/otel/v1/metrics |
| OTLP/HTTP + Protobuf | Log | http://<DataKit-IP>:9529/otel/v1/logs |
| OTLP/gRPC | Trace、Metric、Log | http://<DataKit-IP>:4317 |
如果应用与 DataKit 不在同一主机,需按实际部署调整 DataKit HTTP 监听地址、防火墙或其他网络访问控制。使用 OTLP/gRPC 时,还需要将 addr 改为应用可访问的监听地址,例如 0.0.0.0:4317。不要将 OTLP 接收端口直接暴露到公网。
重启 DataKit 使配置生效:
检查 DataKit HTTP 服务是否可达:
二、应用接入 OpenTelemetry¶
安装 Go SDK 和插桩库¶
在 Go 项目目录中安装官方 SDK、OTLP/HTTP Exporter 和 net/http 插桩库:
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
go mod tidy
go.mod 和 go.sum 会记录依赖版本。生产环境应提交这两个文件,并在升级 OpenTelemetry 依赖后执行编译、单元测试和链路回归测试。
初始化 SDK¶
以下示例同时完成:
- 从
OTEL_SERVICE_NAME和OTEL_RESOURCE_ATTRIBUTES读取资源属性; - 创建 OTLP/HTTP Trace Exporter 和 Metric Exporter;
- 注册全局
TracerProvider、MeterProvider和 W3C 上下文传播器; - 使用
otelhttp包装 HTTP Handler,并创建一个业务子 Span 和自定义 Counter; - 在进程退出时刷新并关闭 Provider,避免缓冲区中的数据丢失。
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
res, err := resource.New(
ctx,
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithOS(),
resource.WithProcess(),
)
if err != nil {
return nil, fmt.Errorf("create resource: %w", err)
}
traceExporter, err := otlptracehttp.New(ctx)
if err != nil {
return nil, fmt.Errorf("create trace exporter: %w", err)
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithResource(res),
sdktrace.WithSampler(
sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0)),
),
sdktrace.WithBatcher(traceExporter),
)
metricExporter, err := otlpmetrichttp.New(ctx)
if err != nil {
_ = tracerProvider.Shutdown(ctx)
return nil, fmt.Errorf("create metric exporter: %w", err)
}
meterProvider := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(
sdkmetric.NewPeriodicReader(
metricExporter,
sdkmetric.WithInterval(30*time.Second),
),
),
)
otel.SetTracerProvider(tracerProvider)
otel.SetMeterProvider(meterProvider)
otel.SetTextMapPropagator(
propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
),
)
shutdown := func(ctx context.Context) error {
return errors.Join(
meterProvider.Shutdown(ctx),
tracerProvider.Shutdown(ctx),
)
}
return shutdown, nil
}
func main() {
ctx, stop := signal.NotifyContext(
context.Background(),
os.Interrupt,
syscall.SIGTERM,
)
defer stop()
shutdown, err := setupOTelSDK(ctx)
if err != nil {
log.Fatal(err)
}
defer func() {
shutdownCtx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
if err := shutdown(shutdownCtx); err != nil {
log.Printf("shutdown OpenTelemetry: %v", err)
}
}()
meter := otel.Meter("example/order-service")
requestCounter, err := meter.Int64Counter("app.request.count")
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.Handle("/hello", otelhttp.NewHandler(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestCtx, span := otel.Tracer("example/order-service").Start(
r.Context(),
"prepare-response",
)
defer span.End()
span.SetAttributes(attribute.String("app.route", "/hello"))
requestCounter.Add(requestCtx, 1)
_, _ = fmt.Fprintln(w, "hello from OpenTelemetry Go SDK")
}),
"GET /hello",
))
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.Printf("shutdown HTTP server: %v", err)
}
}()
log.Println("listening on http://127.0.0.1:8080")
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}
实际项目应为使用到的组件安装对应插桩库。例如,标准库 net/http 使用 otelhttp;其他 Web 框架、数据库或消息队列应从 OpenTelemetry Registry 选择匹配的包,并按照该包的说明包装 Handler、Transport、Client 或 Driver。
配置上报地址并启动¶
下面使用 OTLP/HTTP + Protobuf 上报到本机 DataKit。OTEL_EXPORTER_OTLP_ENDPOINT 是基础地址,Trace 和 Metric Exporter 会分别追加 /v1/traces 和 /v1/metrics,最终对应 DataKit 的 /otel/v1/* 路由。
export OTEL_SERVICE_NAME="order-service"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=prod,service.version=1.0.0,team=backend"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:9529/otel"
export OTEL_EXPORTER_OTLP_INSECURE="true"
export OTEL_EXPORTER_OTLP_COMPRESSION="gzip"
go run .
发起请求以生成 Trace 和 Metric:
Metric 默认每 30 秒导出一次,本示例由 sdkmetric.WithInterval(30*time.Second) 控制。等待一个导出周期后,可在观测云中按 service=order-service 查看链路,并查询 app.request.count 指标。
使用 OTLP/gRPC¶
Go SDK 的 OTLP 传输由代码中使用的 Exporter 包决定。本示例直接使用 otlptracehttp 和 otlpmetrichttp,仅设置 OTEL_EXPORTER_OTLP_PROTOCOL=grpc 不会将其切换为 gRPC。
如需使用 OTLP/gRPC,安装 gRPC Exporter:
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
go mod tidy
然后将代码中的 Exporter 包及初始化函数分别替换为:
并使用不带 /v1/traces、/v1/metrics 路径的 gRPC 地址:
export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:4317"
export OTEL_EXPORTER_OTLP_INSECURE="true"
三、数据上报参数¶
资源参数¶
本示例通过 resource.WithFromEnv() 读取以下标准环境变量:
| 环境变量 | 说明 | 建议值或示例 |
|---|---|---|
OTEL_SERVICE_NAME |
服务名,对应资源属性 service.name。 |
order-service;生产环境必须显式设置。 |
OTEL_RESOURCE_ATTRIBUTES |
资源属性,格式为逗号分隔的 key=value。 |
deployment.environment.name=prod,service.version=1.0.0,team=backend |
建议至少设置 service.name、deployment.environment.name 和 service.version,用于在观测云中进行服务归属、环境过滤和版本分析。自定义资源属性需要加入 DataKit customer_tags 白名单后才会作为标签保留,属性名中的 . 会转换为 _。
OTLP/HTTP 参数¶
otlptracehttp.New() 和 otlpmetrichttp.New() 会直接读取以下环境变量。信号专用参数优先于通用参数:
| 通用环境变量 | 信号专用环境变量 | 说明 | DataKit 示例 |
|---|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT、OTEL_EXPORTER_OTLP_METRICS_ENDPOINT |
通用参数是基础 URL,Exporter 自动追加信号路径;信号专用参数是完整 URL,按原值使用。 | 通用:http://127.0.0.1:9529/otel;Trace:http://127.0.0.1:9529/otel/v1/traces;Metric:http://127.0.0.1:9529/otel/v1/metrics |
OTEL_EXPORTER_OTLP_INSECURE |
OTEL_EXPORTER_OTLP_TRACES_INSECURE、OTEL_EXPORTER_OTLP_METRICS_INSECURE |
是否关闭传输层 TLS。 | DataKit 使用明文 HTTP 时设为 true。 |
OTEL_EXPORTER_OTLP_HEADERS |
OTEL_EXPORTER_OTLP_TRACES_HEADERS、OTEL_EXPORTER_OTLP_METRICS_HEADERS |
请求头,格式为逗号分隔的 key=value。 |
DataKit 配置 expected_headers 时设置对应值。 |
OTEL_EXPORTER_OTLP_TIMEOUT |
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT、OTEL_EXPORTER_OTLP_METRICS_TIMEOUT |
单次导出超时时间,值为毫秒数。 | 根据网络情况设置,例如 10000。 |
OTEL_EXPORTER_OTLP_COMPRESSION |
OTEL_EXPORTER_OTLP_TRACES_COMPRESSION、OTEL_EXPORTER_OTLP_METRICS_COMPRESSION |
OTLP 请求压缩方式。 | 可设为 gzip;不压缩时留空。 |
OTEL_EXPORTER_OTLP_CERTIFICATE |
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE、OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE |
用于校验服务端证书的 PEM CA 文件路径。 | 通过 HTTPS 接收 OTLP 时按证书部署设置。 |
如果应用和 DataKit 不在同一主机,应将示例中的 127.0.0.1 替换为应用可访问的 DataKit 地址。
SDK 代码参数¶
以下参数由 Go SDK 初始化代码控制,不会因为设置同名的通用环境变量而自动生效:
| 配置项 | 示例代码 | 说明 |
|---|---|---|
| Trace 采样 | sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0)) |
1.0 表示根 Trace 全采样;生产环境可按容量调整为 0.1 等比例,并通过 ParentBased 遵循上游采样决定。 |
| Span 批量导出 | sdktrace.WithBatcher(traceExporter) |
生产环境建议使用批量导出;可通过 WithMaxQueueSize、WithMaxExportBatchSize、WithBatchTimeout 和 WithExportTimeout 进一步调整。 |
| Metric 导出周期 | sdkmetric.WithInterval(30*time.Second) |
控制周期性导出间隔;过短会增加应用、网络和存储开销。 |
| 上下文传播 | TraceContext{}、Baggage{} |
使用 W3C traceparent、tracestate 和 baggage。调用链上的服务应保持传播格式兼容。 |
| 资源探测 | resource.WithHost()、WithOS()、WithProcess() |
自动补充主机、操作系统和进程属性。避免在进程参数和资源属性中存放密钥等敏感信息。 |
本示例直接创建 OTLP Exporter,因此需要特别注意:
OTEL_EXPORTER_OTLP_PROTOCOL不会改变已经由代码选定的 HTTP 或 gRPC Exporter;OTEL_TRACES_EXPORTER、OTEL_METRICS_EXPORTER不会关闭本示例中直接创建的 Exporter;- OpenTelemetry Go 核心 SDK 当前不会自动应用所有通用 SDK 环境变量,尤其不要假设
OTEL_SDK_DISABLED、OTEL_TRACES_SAMPLER或OTEL_PROPAGATORS会在自定义初始化代码中自动生效; - 如需通过标准环境变量动态选择和初始化 Exporter,可评估官方 Contrib 的
autoexport包,并在测试环境验证其行为。
验证与排查¶
- 执行
curl http://127.0.0.1:9529/v1/ping,确认应用可以访问 DataKit; - 启动应用并访问
/hello,确认应用日志中没有create trace exporter、create metric exporter或 OTLP export error; - 等待至少一个 Metric 导出周期;
- 在观测云的 APM 服务列表中按
order-service查询 Trace; - 查询不到数据时,检查 DataKit
opentelemetry采集器配置、应用到 DataKit 的网络、上报 URL 和 DataKit 日志; - 出现重复 Span 时,检查同一 Handler、Transport、数据库 Client 或 Driver 是否被重复包装。