Go 语言 OpenTelemetry 接入指南

本文档提供 Go 语言应用接入 OpenTelemetry 的完整指南,包括零代码接入和 SDK 手动接入两种方式。

目录


概述

Go 语言特性

Go 语言的 OpenTelemetry 支持具有以下特点:

  • 成熟稳定: API 和 SDK 已达到 1.0 稳定版本
  • 高性能: 极低的性能开销(< 5% CPU, 10-20MB 内存)
  • 丰富的埋点库: 支持 50+ 主流框架和库
  • Context 传播: 完美契合 Go 的 context.Context 机制
  • 并发安全: 所有 API 都是线程安全的

版本要求

  • Go 版本: 1.20+ (推荐 1.21+)
  • OpenTelemetry Go: v1.20.0+
  • 支持架构: amd64, arm64, 386, arm

支持的信号

  • ✅ Traces: 完全支持
  • ✅ Metrics: 完全支持
  • ✅ Logs: 支持(通过日志库集成)

适用场景

  • Web 服务(HTTP/gRPC)
  • 微服务架构
  • 数据库应用
  • 消息队列应用
  • 批处理作业

零代码接入(推荐)

零代码接入无需修改应用代码,通过环境变量配置即可自动完成埋点。

方式 1: 使用官方 Auto-Instrumentation(实验性)

Go 官方的自动埋点目前处于实验阶段,支持有限。对于生产环境,建议使用 loongsuite-go-agent 或 SDK 手动接入。

安装

go install go.opentelemetry.io/auto@latest

配置环境变量

# 服务信息
export OTEL_SERVICE_NAME=my-go-service
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production
# OTLP Exporter
export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
# Traces 配置
export OTEL_TRACES_EXPORTER=otlp
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
# Metrics 配置
export OTEL_METRICS_EXPORTER=otlp
export OTEL_METRIC_EXPORT_INTERVAL=60000
# Logs 配置
export OTEL_LOGS_EXPORTER=otlp

启动应用

# 使用 auto-instrumentation 启动
go run go.opentelemetry.io/auto -service.name=my-service ./main.go
# 或编译后运行
go build -o myapp
go run go.opentelemetry.io/auto -service.name=my-service ./myapp

方式 2: 使用 loongsuite-go-agent(推荐)

loongsuite-go-agent 是阿里巴巴开源的 Go 自动埋点工具,支持编译时注入,功能更强大。

详细文档: Go loongsuite-go-agent 接入指南

快速开始

# 1. 安装 otel 工具
curl -sSf https://raw.githubusercontent.com/alibaba/loongsuite-go-agent/main/install.sh | sh
# 2. 初始化项目
cd your-project
otel init
# 3. 配置环境变量
export OTEL_SERVICE_NAME=my-service
export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
# 4. 使用 otel 工具编译
otel build -o myapp
# 5. 运行
./myapp

支持的框架(部分)

  • Web: Gin, Echo, Fiber, Chi, Gorilla Mux, Hertz, Kratos
  • 数据库: database/sql, GORM, MongoDB, Redis, Elasticsearch
  • RPC: gRPC, Kitex, Dubbo-go, RPCX
  • 消息队列: Kafka, RabbitMQ, RocketMQ, NATS
  • HTTP 客户端: net/http, fasthttp
  • 日志: zap, logrus, zerolog, slog

完整列表: https://github.com/alibaba/loongsuite-go-agent#supported-libraries

方式 3: 使用 Kubernetes Operator

如果您的应用运行在 Kubernetes 中,可以使用 OpenTelemetry Operator 自动注入。

安装 Operator

kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml

创建 Instrumentation 资源

apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
namespace: default
spec:
exporter:
endpoint: uptrace-grpc.yodao.cn:4317
env:
- name: OTEL_EXPORTER_OTLP_HEADERS
value: uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "0.1"

部署应用

在 Pod 上添加 annotation:

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-go: "true"
spec:
containers:
- name: app
image: my-go-app:latest

验证零代码接入

启动应用后,访问您的服务端点,然后在 APM 平台查看数据:

  1. Traces: 查看调用链路
  2. Metrics: 查看请求量、延迟等指标
  3. Service Map: 查看服务拓扑图

快速验证脚本

#!/bin/bash
# verify-instrumentation.sh
echo "Checking service..."
curl -s http://localhost:8080/health > /dev/null
echo "Waiting for traces to be exported..."
sleep 5
echo "Check your APM platform for service: $OTEL_SERVICE_NAME"
echo "Endpoint: $OTEL_EXPORTER_OTLP_ENDPOINT"

SDK 手动接入

SDK 手动接入提供更精细的控制和自定义能力。

安装依赖

# 核心 SDK
go get go.opentelemetry.io/otel@latest
go get go.opentelemetry.io/otel/sdk@latest
# OTLP Exporter
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc@latest
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc@latest
# 埋点库(可选)
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp@latest
go get go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc@latest

基础初始化

1. 创建 OpenTelemetry 初始化函数

// otel.go
package main
import (
"context"
"fmt"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
// InitOpenTelemetry 初始化 OpenTelemetry
func InitOpenTelemetry(ctx context.Context) (func(context.Context) error, error) {
// 1. 创建 Resource
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("my-go-service"),
semconv.ServiceVersion("1.0.0"),
attribute.String("deployment.environment", "production"),
),
resource.WithProcess(),
resource.WithOS(),
resource.WithContainer(),
resource.WithHost(),
)
if err != nil {
return nil, fmt.Errorf("failed to create resource: %w", err)
}
// 2. 创建 Trace Exporter
traceExporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("api.uptrace.dev:4317"),
otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")),
otlptracegrpc.WithHeaders(map[string]string{
"uptrace-dsn": "http://token@uptrace-grpc.yodao.cn/project",
}),
)
if err != nil {
return nil, fmt.Errorf("failed to create trace exporter: %w", err)
}
// 3. 创建 Trace Provider
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithResource(res),
sdktrace.WithBatcher(traceExporter,
sdktrace.WithBatchTimeout(5*time.Second),
sdktrace.WithMaxExportBatchSize(512),
),
sdktrace.WithSampler(sdktrace.ParentBased(
sdktrace.TraceIDRatioBased(0.1), // 10% 采样
)),
)
otel.SetTracerProvider(tracerProvider)
// 4. 创建 Metric Exporter
metricExporter, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithEndpoint("api.uptrace.dev:4317"),
otlpmetricgrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")),
otlpmetricgrpc.WithHeaders(map[string]string{
"uptrace-dsn": "http://token@uptrace-grpc.yodao.cn/project",
}),
)
if err != nil {
return nil, fmt.Errorf("failed to create metric exporter: %w", err)
}
// 5. 创建 Metric Provider
meterProvider := metric.NewMeterProvider(
metric.WithResource(res),
metric.WithReader(
metric.NewPeriodicReader(metricExporter,
metric.WithInterval(60*time.Second),
),
),
)
otel.SetMeterProvider(meterProvider)
// 6. 设置 Propagator
otel.SetTextMapPropagator(
propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
),
)
// 7. 返回 Shutdown 函数
return func(ctx context.Context) error {
var err error
if e := tracerProvider.Shutdown(ctx); e != nil {
err = e
}
if e := meterProvider.Shutdown(ctx); e != nil {
if err != nil {
err = fmt.Errorf("%v; %w", err, e)
} else {
err = e
}
}
return err
}, nil
}

2. 在 main 函数中初始化

// main.go
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 初始化 OpenTelemetry
shutdown, err := InitOpenTelemetry(ctx)
if err != nil {
log.Fatalf("Failed to initialize OpenTelemetry: %v", err)
}
// 确保在程序退出时调用 shutdown
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := shutdown(ctx); err != nil {
log.Printf("Error shutting down OpenTelemetry: %v", err)
}
}()
// 优雅关闭
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
// 启动应用
go startServer()
<-sigCh
log.Println("Shutting down gracefully...")
}
func startServer() {
// 应用逻辑
}

Traces - 手动埋点

创建和使用 Span

package main
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
var tracer = otel.Tracer("my-service")
// ProcessOrder 处理订单
func ProcessOrder(ctx context.Context, orderID string) error {
// 创建 Span
ctx, span := tracer.Start(ctx, "process_order",
trace.WithAttributes(
attribute.String("order.id", orderID),
attribute.String("operation", "process"),
),
trace.WithSpanKind(trace.SpanKindInternal),
)
defer span.End()
// 添加事件
span.AddEvent("order_validation_started")
// 验证订单
if err := validateOrder(ctx, orderID); err != nil {
// 记录错误
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
span.AddEvent("order_validated")
// 处理支付
if err := processPayment(ctx, orderID); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "payment failed")
return err
}
span.AddEvent("payment_processed")
// 设置成功状态
span.SetStatus(codes.Ok, "order processed successfully")
span.SetAttributes(attribute.Bool("order.success", true))
return nil
}
func validateOrder(ctx context.Context, orderID string) error {
ctx, span := tracer.Start(ctx, "validate_order")
defer span.End()
// 业务逻辑
span.SetAttributes(attribute.String("order.id", orderID))
return nil
}
func processPayment(ctx context.Context, orderID string) error {
ctx, span := tracer.Start(ctx, "process_payment",
trace.WithAttributes(
attribute.String("order.id", orderID),
attribute.String("payment.method", "credit_card"),
),
)
defer span.End()
// 支付逻辑
return nil
}

Span 属性和事件

func ExampleSpanAttributes(ctx context.Context) {
ctx, span := tracer.Start(ctx, "example_operation")
defer span.End()
// 设置属性
span.SetAttributes(
attribute.String("user.id", "12345"),
attribute.String("user.email", "user@example.com"),
attribute.Int("user.age", 30),
attribute.Bool("user.premium", true),
attribute.StringSlice("user.roles", []string{"admin", "user"}),
)
// 添加事件
span.AddEvent("cache_miss",
trace.WithAttributes(
attribute.String("cache.key", "user:12345"),
attribute.String("cache.type", "redis"),
),
)
// 添加带时间戳的事件
span.AddEvent("retry_attempt",
trace.WithTimestamp(time.Now()),
trace.WithAttributes(
attribute.Int("retry.count", 2),
attribute.String("retry.reason", "timeout"),
),
)
}

Context 传播

// HTTP 客户端传播
func MakeHTTPRequest(ctx context.Context, url string) (*http.Response, error) {
ctx, span := tracer.Start(ctx, "http_request",
trace.WithAttributes(
attribute.String("http.url", url),
attribute.String("http.method", "GET"),
),
)
defer span.End()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
// 注入 trace context 到 HTTP headers
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
resp, err := http.DefaultClient.Do(req)
if err != nil {
span.RecordError(err)
return nil, err
}
span.SetAttributes(attribute.Int("http.status_code", resp.StatusCode))
return resp, nil
}
// HTTP 服务端提取
func ExtractContext(r *http.Request) context.Context {
// 从 HTTP headers 提取 trace context
ctx := otel.GetTextMapPropagator().Extract(
r.Context(),
propagation.HeaderCarrier(r.Header),
)
return ctx
}

Metrics - 指标采集

Counter(计数器)

package main
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
var meter = otel.Meter("my-service")
// 创建 Counter
var (
requestCounter metric.Int64Counter
)
func init() {
var err error
requestCounter, err = meter.Int64Counter(
"http.server.requests",
metric.WithDescription("Total number of HTTP requests"),
metric.WithUnit("1"),
)
if err != nil {
panic(err)
}
}
func HandleRequest(ctx context.Context, method, route string, statusCode int) {
// 增加计数
requestCounter.Add(ctx, 1,
metric.WithAttributes(
attribute.String("http.method", method),
attribute.String("http.route", route),
attribute.Int("http.status_code", statusCode),
),
)
}

Histogram(直方图)

var (
requestDuration metric.Float64Histogram
)
func init() {
var err error
requestDuration, err = meter.Float64Histogram(
"http.server.duration",
metric.WithDescription("HTTP request duration"),
metric.WithUnit("ms"),
metric.WithExplicitBucketBoundaries(
5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000,
),
)
if err != nil {
panic(err)
}
}
func RecordDuration(ctx context.Context, duration float64, method, route string) {
requestDuration.Record(ctx, duration,
metric.WithAttributes(
attribute.String("http.method", method),
attribute.String("http.route", route),
),
)
}

UpDownCounter(升降计数器)

var (
activeConnections metric.Int64UpDownCounter
)
func init() {
var err error
activeConnections, err = meter.Int64UpDownCounter(
"http.server.active_connections",
metric.WithDescription("Number of active HTTP connections"),
metric.WithUnit("1"),
)
if err != nil {
panic(err)
}
}
func ConnectionOpened(ctx context.Context) {
activeConnections.Add(ctx, 1)
}
func ConnectionClosed(ctx context.Context) {
activeConnections.Add(ctx, -1)
}

Gauge(仪表)

var (
memoryUsage metric.Int64ObservableGauge
)
func init() {
var err error
memoryUsage, err = meter.Int64ObservableGauge(
"runtime.memory.heap_alloc",
metric.WithDescription("Heap memory allocated"),
metric.WithUnit("bytes"),
)
if err != nil {
panic(err)
}
// 注册回调函数
_, err = meter.RegisterCallback(
func(_ context.Context, o metric.Observer) error {
var m runtime.MemStats
runtime.ReadMemStats(&m)
o.ObserveInt64(memoryUsage, int64(m.HeapAlloc))
return nil
},
memoryUsage,
)
if err != nil {
panic(err)
}
}

Logs - 日志集成

集成 Zap

package main
import (
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// NewLogger 创建带 trace 信息的 logger
func NewLogger() *zap.Logger {
config := zap.NewProductionConfig()
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, _ := config.Build(
zap.AddCallerSkip(1),
zap.AddStacktrace(zapcore.ErrorLevel),
)
return logger
}
// LogWithTrace 记录带 trace 信息的日志
func LogWithTrace(ctx context.Context, logger *zap.Logger, msg string, fields ...zap.Field) {
span := trace.SpanFromContext(ctx)
if span.SpanContext().IsValid() {
fields = append(fields,
zap.String("trace_id", span.SpanContext().TraceID().String()),
zap.String("span_id", span.SpanContext().SpanID().String()),
)
}
logger.Info(msg, fields...)
}
// 使用示例
func Example(ctx context.Context) {
logger := NewLogger()
defer logger.Sync()
ctx, span := tracer.Start(ctx, "example_operation")
defer span.End()
LogWithTrace(ctx, logger, "Processing request",
zap.String("user_id", "12345"),
zap.String("action", "create"),
)
}

集成 Logrus

package main
import (
"context"
"github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/trace"
)
// TraceHook 添加 trace 信息到日志
type TraceHook struct{}
func (hook *TraceHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (hook *TraceHook) Fire(entry *logrus.Entry) error {
if ctx, ok := entry.Data["ctx"].(context.Context); ok {
span := trace.SpanFromContext(ctx)
if span.SpanContext().IsValid() {
entry.Data["trace_id"] = span.SpanContext().TraceID().String()
entry.Data["span_id"] = span.SpanContext().SpanID().String()
}
delete(entry.Data, "ctx")
}
return nil
}
// NewLogrusLogger 创建 logger
func NewLogrusLogger() *logrus.Logger {
logger := logrus.New()
logger.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02T15:04:05.000Z07:00",
})
logger.AddHook(&TraceHook{})
return logger
}
// 使用示例
func ExampleLogrus(ctx context.Context) {
logger := NewLogrusLogger()
ctx, span := tracer.Start(ctx, "example_operation")
defer span.End()
logger.WithFields(logrus.Fields{
"ctx": ctx,
"user_id": "12345",
"action": "create",
}).Info("Processing request")
}

集成 slog (Go 1.21+)

package main
import (
"context"
"log/slog"
"os"
"go.opentelemetry.io/otel/trace"
)
// TraceHandler 包装 slog.Handler 添加 trace 信息
type TraceHandler struct {
slog.Handler
}
func (h *TraceHandler) Handle(ctx context.Context, r slog.Record) error {
span := trace.SpanFromContext(ctx)
if span.SpanContext().IsValid() {
r.AddAttrs(
slog.String("trace_id", span.SpanContext().TraceID().String()),
slog.String("span_id", span.SpanContext().SpanID().String()),
)
}
return h.Handler.Handle(ctx, r)
}
// NewSlogLogger 创建 logger
func NewSlogLogger() *slog.Logger {
handler := &TraceHandler{
Handler: slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}),
}
return slog.New(handler)
}
// 使用示例
func ExampleSlog(ctx context.Context) {
logger := NewSlogLogger()
ctx, span := tracer.Start(ctx, "example_operation")
defer span.End()
logger.InfoContext(ctx, "Processing request",
slog.String("user_id", "12345"),
slog.String("action", "create"),
)
}

框架集成示例

Gin Framework

package main
import (
"github.com/gin-gonic/gin"
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
func main() {
// 初始化 OpenTelemetry
shutdown, err := InitOpenTelemetry(context.Background())
if err != nil {
log.Fatal(err)
}
defer shutdown(context.Background())
// 创建 Gin 引擎
r := gin.Default()
// 添加 OpenTelemetry 中间件
r.Use(otelgin.Middleware("my-service"))
// 定义路由
r.GET("/users/:id", GetUser)
r.POST("/users", CreateUser)
// 启动服务器
r.Run(":8080")
}
var tracer = otel.Tracer("my-service")
func GetUser(c *gin.Context) {
ctx := c.Request.Context()
// 手动创建子 Span
ctx, span := tracer.Start(ctx, "get_user_handler")
defer span.End()
userID := c.Param("id")
span.SetAttributes(attribute.String("user.id", userID))
// 查询用户
user, err := queryUser(ctx, userID)
if err != nil {
span.RecordError(err)
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, user)
}
func CreateUser(c *gin.Context) {
ctx := c.Request.Context()
ctx, span := tracer.Start(ctx, "create_user_handler")
defer span.End()
var user User
if err := c.ShouldBindJSON(&user); err != nil {
span.RecordError(err)
c.JSON(400, gin.H{"error": err.Error()})
return
}
if err := saveUser(ctx, &user); err != nil {
span.RecordError(err)
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(201, user)
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func queryUser(ctx context.Context, id string) (*User, error) {
ctx, span := tracer.Start(ctx, "query_user",
trace.WithAttributes(attribute.String("db.operation", "SELECT")),
)
defer span.End()
// 数据库查询逻辑
return &User{ID: id, Name: "John Doe"}, nil
}
func saveUser(ctx context.Context, user *User) error {
ctx, span := tracer.Start(ctx, "save_user",
trace.WithAttributes(attribute.String("db.operation", "INSERT")),
)
defer span.End()
// 数据库插入逻辑
return nil
}

Echo Framework

package main
import (
"github.com/labstack/echo/v4"
"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho"
)
func main() {
// 初始化 OpenTelemetry
shutdown, err := InitOpenTelemetry(context.Background())
if err != nil {
log.Fatal(err)
}
defer shutdown(context.Background())
// 创建 Echo 实例
e := echo.New()
// 添加 OpenTelemetry 中间件
e.Use(otelecho.Middleware("my-service"))
// 定义路由
e.GET("/users/:id", func(c echo.Context) error {
ctx := c.Request().Context()
ctx, span := tracer.Start(ctx, "get_user")
defer span.End()
userID := c.Param("id")
span.SetAttributes(attribute.String("user.id", userID))
return c.JSON(200, map[string]string{"id": userID})
})
// 启动服务器
e.Start(":8080")
}

gRPC Server

package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
pb "your-package/proto"
)
type server struct {
pb.UnimplementedUserServiceServer
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
// ctx 已经包含了 trace 信息
ctx, span := tracer.Start(ctx, "GetUser")
defer span.End()
span.SetAttributes(attribute.String("user.id", req.GetId()))
// 业务逻辑
return &pb.GetUserResponse{
Id: req.GetId(),
Name: "John Doe",
}, nil
}
func main() {
// 初始化 OpenTelemetry
shutdown, err := InitOpenTelemetry(context.Background())
if err != nil {
log.Fatal(err)
}
defer shutdown(context.Background())
// 创建 gRPC 服务器
s := grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler()),
)
pb.RegisterUserServiceServer(s, &server{})
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatal(err)
}
log.Println("gRPC server listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatal(err)
}
}

gRPC Client

package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
pb "your-package/proto"
)
func main() {
// 创建 gRPC 连接
conn, err := grpc.NewClient(
"localhost:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
// 调用服务
ctx, span := tracer.Start(context.Background(), "call_get_user")
defer span.End()
resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: "123"})
if err != nil {
span.RecordError(err)
log.Fatal(err)
}
log.Printf("User: %+v", resp)
}

Database - database/sql

package main
import (
"context"
"database/sql"
_ "github.com/go-sql-driver/mysql"
"go.opentelemetry.io/contrib/instrumentation/database/sql/otelsql"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)
func main() {
// 注册带埋点的驱动
driverName, err := otelsql.Register("mysql",
otelsql.WithAttributes(
semconv.DBSystemMySQL,
),
)
if err != nil {
log.Fatal(err)
}
// 打开数据库连接
db, err := sql.Open(driverName, "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 记录统计信息
if err := otelsql.RecordStats(db); err != nil {
log.Fatal(err)
}
// 使用数据库
ctx, span := tracer.Start(context.Background(), "database_query")
defer span.End()
rows, err := db.QueryContext(ctx, "SELECT id, name FROM users WHERE id = ?", 123)
if err != nil {
span.RecordError(err)
log.Fatal(err)
}
defer rows.Close()
}

Database - GORM

package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"go.opentelemetry.io/contrib/instrumentation/gorm.io/gorm/otelgorm"
)
func main() {
dsn := "user:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
// 添加 OpenTelemetry 插件
if err := db.Use(otelgorm.NewPlugin()); err != nil {
log.Fatal(err)
}
// 使用 GORM
ctx, span := tracer.Start(context.Background(), "gorm_query")
defer span.End()
var users []User
result := db.WithContext(ctx).Find(&users)
if result.Error != nil {
span.RecordError(result.Error)
}
}

Redis - go-redis

package main
import (
"context"
"github.com/redis/go-redis/v9"
"go.opentelemetry.io/contrib/instrumentation/github.com/redis/go-redis/v9/otelredis"
)
func main() {
// 创建 Redis 客户端
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
// 添加 OpenTelemetry Hook
if err := otelredis.InstrumentTracing(rdb); err != nil {
log.Fatal(err)
}
if err := otelredis.InstrumentMetrics(rdb); err != nil {
log.Fatal(err)
}
// 使用 Redis
ctx, span := tracer.Start(context.Background(), "redis_operation")
defer span.End()
err := rdb.Set(ctx, "key", "value", 0).Err()
if err != nil {
span.RecordError(err)
}
val, err := rdb.Get(ctx, "key").Result()
if err != nil {
span.RecordError(err)
}
log.Println(val)
}

Kafka - sarama

package main
import (
"github.com/IBM/sarama"
"go.opentelemetry.io/contrib/instrumentation/github.com/IBM/sarama/otelsarama"
)
func main() {
config := sarama.NewConfig()
config.Version = sarama.V2_8_0_0
config.Producer.Return.Successes = true
// 创建生产者
producer, err := sarama.NewSyncProducer([]string{"localhost:9092"}, config)
if err != nil {
log.Fatal(err)
}
defer producer.Close()
// 包装生产者
producer = otelsarama.WrapSyncProducer(config, producer)
// 发送消息
ctx, span := tracer.Start(context.Background(), "kafka_produce")
defer span.End()
// 注入 trace context
msg := &sarama.ProducerMessage{
Topic: "test-topic",
Value: sarama.StringEncoder("test message"),
}
// 传播 context
otel.GetTextMapPropagator().Inject(ctx, otelsarama.NewProducerMessageCarrier(msg))
partition, offset, err := producer.SendMessage(msg)
if err != nil {
span.RecordError(err)
}
log.Printf("Message sent to partition %d at offset %d\n", partition, offset)
}

高级配置

自定义采样策略

基于 Span 名称的采样

package main
import (
"strings"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
// CustomSampler 自定义采样器
type CustomSampler struct {
defaultSampler sdktrace.Sampler
alwaysSample []string // 始终采样的 Span 名称
neverSample []string // 从不采样的 Span 名称
}
func NewCustomSampler(defaultRate float64, alwaysSample, neverSample []string) sdktrace.Sampler {
return &CustomSampler{
defaultSampler: sdktrace.TraceIDRatioBased(defaultRate),
alwaysSample: alwaysSample,
neverSample: neverSample,
}
}
func (s *CustomSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
spanName := p.Name
// 检查是否需要总是采样
for _, name := range s.alwaysSample {
if strings.Contains(spanName, name) {
return sdktrace.SamplingResult{
Decision: sdktrace.RecordAndSample,
Tracestate: p.ParentContext.TraceState(),
}
}
}
// 检查是否从不采样
for _, name := range s.neverSample {
if strings.Contains(spanName, name) {
return sdktrace.SamplingResult{
Decision: sdktrace.Drop,
Tracestate: p.ParentContext.TraceState(),
}
}
}
// 使用默认采样策略
return s.defaultSampler.ShouldSample(p)
}
func (s *CustomSampler) Description() string {
return "CustomSampler"
}
// 使用示例
func InitWithCustomSampler() *sdktrace.TracerProvider {
sampler := NewCustomSampler(
0.1, // 默认 10% 采样
[]string{"/checkout", "/payment"}, // 始终采样
[]string{"/health", "/metrics"}, // 从不采样
)
return sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.ParentBased(sampler)),
)
}

基于属性的采样

// AttributeBasedSampler 基于属性的采样器
type AttributeBasedSampler struct {
defaultSampler sdktrace.Sampler
}
func NewAttributeBasedSampler(defaultRate float64) sdktrace.Sampler {
return &AttributeBasedSampler{
defaultSampler: sdktrace.TraceIDRatioBased(defaultRate),
}
}
func (s *AttributeBasedSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
// 检查特定属性
for _, attr := range p.Attributes {
if string(attr.Key) == "important" && attr.Value.AsBool() {
// 重要请求总是采样
return sdktrace.SamplingResult{
Decision: sdktrace.RecordAndSample,
Tracestate: p.ParentContext.TraceState(),
}
}
// 错误请求总是采样
if string(attr.Key) == "error" && attr.Value.AsBool() {
return sdktrace.SamplingResult{
Decision: sdktrace.RecordAndSample,
Tracestate: p.ParentContext.TraceState(),
}
}
}
return s.defaultSampler.ShouldSample(p)
}
func (s *AttributeBasedSampler) Description() string {
return "AttributeBasedSampler"
}

Span Processor

自定义属性添加

// AttributeAdder 添加自定义属性到所有 Span
type AttributeAdder struct {
sdktrace.SpanProcessor
attributes []attribute.KeyValue
}
func NewAttributeAdder(processor sdktrace.SpanProcessor, attrs []attribute.KeyValue) sdktrace.SpanProcessor {
return &AttributeAdder{
SpanProcessor: processor,
attributes: attrs,
}
}
func (a *AttributeAdder) OnStart(parent context.Context, s sdktrace.ReadWriteSpan) {
// 添加自定义属性
s.SetAttributes(a.attributes...)
a.SpanProcessor.OnStart(parent, s)
}
func (a *AttributeAdder) OnEnd(s sdktrace.ReadOnlySpan) {
a.SpanProcessor.OnEnd(s)
}
func (a *AttributeAdder) Shutdown(ctx context.Context) error {
return a.SpanProcessor.Shutdown(ctx)
}
func (a *AttributeAdder) ForceFlush(ctx context.Context) error {
return a.SpanProcessor.ForceFlush(ctx)
}
// 使用示例
func InitWithAttributeAdder() *sdktrace.TracerProvider {
exporter, _ := otlptracegrpc.New(context.Background())
bsp := sdktrace.NewBatchSpanProcessor(exporter)
// 包装处理器
processor := NewAttributeAdder(bsp, []attribute.KeyValue{
attribute.String("environment", "production"),
attribute.String("region", "us-east-1"),
attribute.String("version", "1.0.0"),
})
return sdktrace.NewTracerProvider(
sdktrace.WithSpanProcessor(processor),
)
}

过滤 Span

// FilteringProcessor 过滤特定 Span
type FilteringProcessor struct {
sdktrace.SpanProcessor
filter func(sdktrace.ReadOnlySpan) bool
}
func NewFilteringProcessor(processor sdktrace.SpanProcessor, filter func(sdktrace.ReadOnlySpan) bool) sdktrace.SpanProcessor {
return &FilteringProcessor{
SpanProcessor: processor,
filter: filter,
}
}
func (f *FilteringProcessor) OnEnd(s sdktrace.ReadOnlySpan) {
// 只处理符合条件的 Span
if f.filter(s) {
f.SpanProcessor.OnEnd(s)
}
}
// 使用示例:过滤掉健康检查请求
func InitWithFiltering() *sdktrace.TracerProvider {
exporter, _ := otlptracegrpc.New(context.Background())
bsp := sdktrace.NewBatchSpanProcessor(exporter)
processor := NewFilteringProcessor(bsp, func(s sdktrace.ReadOnlySpan) bool {
// 过滤掉健康检查
return !strings.Contains(s.Name(), "/health")
})
return sdktrace.NewTracerProvider(
sdktrace.WithSpanProcessor(processor),
)
}

多 Exporter 配置

func InitWithMultipleExporters(ctx context.Context) (*sdktrace.TracerProvider, error) {
res, _ := resource.New(ctx, resource.WithAttributes(
semconv.ServiceName("my-service"),
))
// OTLP Exporter (主要后端)
otlpExporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("api.uptrace.dev:4317"),
otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")),
otlptracegrpc.WithHeaders(map[string]string{
"uptrace-dsn": "http://token@uptrace-grpc.yodao.cn/project",
}),
)
if err != nil {
return nil, err
}
// Jaeger Exporter (备份)
jaegerExporter, err := jaeger.New(jaeger.WithCollectorEndpoint(
jaeger.WithEndpoint("http://localhost:14268/api/traces"),
))
if err != nil {
return nil, err
}
// Console Exporter (本地调试)
consoleExporter, err := stdouttrace.New(
stdouttrace.WithPrettyPrint(),
)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithResource(res),
// 添加多个处理器
sdktrace.WithBatcher(otlpExporter),
sdktrace.WithBatcher(jaegerExporter),
sdktrace.WithBatcher(consoleExporter),
)
return tp, nil
}

资源检测

import (
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)
func CreateResourceWithDetectors(ctx context.Context) (*resource.Resource, error) {
return resource.New(ctx,
// 手动设置的资源属性
resource.WithAttributes(
semconv.ServiceName("my-service"),
semconv.ServiceVersion("1.0.0"),
),
// 自动检测进程信息
resource.WithProcess(),
// 自动检测主机信息
resource.WithHost(),
// 自动检测操作系统信息
resource.WithOS(),
// 自动检测容器信息
resource.WithContainer(),
// 自动检测环境变量中的资源属性
resource.WithFromEnv(),
// 自动检测 TelemetrySDK 信息
resource.WithTelemetrySDK(),
)
}

性能优化配置

func InitOptimizedProvider(ctx context.Context) (*sdktrace.TracerProvider, error) {
exporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("api.uptrace.dev:4317"),
otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")),
// 启用压缩
otlptracegrpc.WithCompressor("gzip"),
// 设置超时
otlptracegrpc.WithTimeout(10*time.Second),
// 设置重试
otlptracegrpc.WithRetry(otlptracegrpc.RetryConfig{
Enabled: true,
InitialInterval: 1 * time.Second,
MaxInterval: 30 * time.Second,
MaxElapsedTime: 5 * time.Minute,
}),
)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter,
// 批量大小
sdktrace.WithMaxExportBatchSize(512),
// 批量超时
sdktrace.WithBatchTimeout(5*time.Second),
// 队列大小
sdktrace.WithMaxQueueSize(2048),
// 导出超时
sdktrace.WithExportTimeout(30*time.Second),
),
// 采样率
sdktrace.WithSampler(sdktrace.ParentBased(
sdktrace.TraceIDRatioBased(0.1),
)),
// Span 限制
sdktrace.WithSpanLimits(sdktrace.SpanLimits{
AttributeValueLengthLimit: 1024,
AttributeCountLimit: 128,
EventCountLimit: 128,
LinkCountLimit: 128,
AttributePerEventCountLimit: 128,
AttributePerLinkCountLimit: 128,
}),
)
return tp, nil
}

最佳实践

1. Context 传递

正确做法: 始终传递 context

// ✅ 正确
func ProcessOrder(ctx context.Context, orderID string) error {
ctx, span := tracer.Start(ctx, "process_order")
defer span.End()
// 传递 context 给下游函数
if err := validateOrder(ctx, orderID); err != nil {
return err
}
if err := chargePayment(ctx, orderID); err != nil {
return err
}
return nil
}

错误做法: 创建新的 context

// ❌ 错误
func ProcessOrder(ctx context.Context, orderID string) error {
ctx, span := tracer.Start(ctx, "process_order")
defer span.End()
// 不要创建新的 context.Background()
if err := validateOrder(context.Background(), orderID); err != nil {
return err
}
return nil
}

2. Span 命名

正确做法: 使用描述性名称

// ✅ 正确
ctx, span := tracer.Start(ctx, "database.query.users.by_id")
ctx, span := tracer.Start(ctx, "http.client.post.payment_gateway")
ctx, span := tracer.Start(ctx, "cache.get.user_profile")

错误做法: 使用模糊或过长的名称

// ❌ 错误
ctx, span := tracer.Start(ctx, "do_something")
ctx, span := tracer.Start(ctx, "query")
ctx, span := tracer.Start(ctx, fmt.Sprintf("get_user_%s_from_database_with_params_%v", id, params))

3. 错误处理

正确做法: 记录错误并设置状态

// ✅ 正确
func QueryDatabase(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "query_database")
defer span.End()
err := db.Query(ctx, "SELECT * FROM users")
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
span.SetStatus(codes.Ok, "")
return nil
}

错误做法: 忽略错误或不设置状态

// ❌ 错误
func QueryDatabase(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "query_database")
defer span.End()
err := db.Query(ctx, "SELECT * FROM users")
// 没有记录错误
return err
}

4. 资源管理

正确做法: 始终调用 defer span.End()

// ✅ 正确
func Operation(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "operation")
defer span.End()
// 业务逻辑
return nil
}

错误做法: 忘记调用 span.End()

// ❌ 错误
func Operation(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "operation")
// 忘记 defer span.End()
return nil
}

5. 属性使用

正确做法: 使用语义约定

// ✅ 正确
span.SetAttributes(
semconv.HTTPMethod("GET"),
semconv.HTTPURL("https://api.example.com/users"),
semconv.HTTPStatusCode(200),
semconv.DBSystem("mysql"),
semconv.DBName("users_db"),
semconv.DBStatement("SELECT * FROM users WHERE id = ?"),
)

错误做法: 使用自定义键名

// ❌ 错误(除非必要)
span.SetAttributes(
attribute.String("method", "GET"),
attribute.String("url", "https://api.example.com/users"),
attribute.Int("status", 200),
)

6. 避免过度埋点

正确做法: 只在关键路径埋点

// ✅ 正确
func ProcessOrder(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "process_order")
defer span.End()
// 关键操作
validateOrder(ctx)
chargePayment(ctx)
sendEmail(ctx)
return nil
}

错误做法: 对每个小函数都埋点

// ❌ 错误(过度埋点)
func ProcessOrder(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "process_order")
defer span.End()
validateID(ctx) // 太细粒度
checkFormat(ctx) // 太细粒度
validateAge(ctx) // 太细粒度
validateEmail(ctx) // 太细粒度
return nil
}

7. Metrics 最佳实践

var (
// ✅ 正确:在包级别创建仪表
requestCounter metric.Int64Counter
requestDuration metric.Float64Histogram
)
func init() {
meter := otel.Meter("my-service")
requestCounter, _ = meter.Int64Counter("requests")
requestDuration, _ = meter.Float64Histogram("request_duration")
}
func HandleRequest(ctx context.Context) {
start := time.Now()
// 业务逻辑
duration := time.Since(start).Milliseconds()
// 记录指标
requestCounter.Add(ctx, 1)
requestDuration.Record(ctx, float64(duration))
}

8. 优雅关闭

func main() {
ctx := context.Background()
shutdown, err := InitOpenTelemetry(ctx)
if err != nil {
log.Fatal(err)
}
// ✅ 正确:确保调用 shutdown
defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := shutdown(ctx); err != nil {
log.Printf("Error shutting down: %v", err)
}
}()
// 应用逻辑
}

故障排查

常见问题

1. 数据未发送到后端

症状: APM 平台看不到数据

排查步骤:

  1. 检查环境变量配置:
echo $OTEL_SERVICE_NAME
echo $OTEL_EXPORTER_OTLP_ENDPOINT
echo $OTEL_EXPORTER_OTLP_HEADERS
  1. 检查网络连接:
curl -v uptrace-grpc.yodao.cn:4317
  1. 启用调试日志:
import "go.opentelemetry.io/otel"
import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
// 临时添加 console exporter
consoleExporter, _ := stdouttrace.New(stdouttrace.WithPrettyPrint())
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(consoleExporter),
)
  1. 检查采样率:
export OTEL_TRACES_SAMPLER_ARG=1.0 # 100% 采样用于调试

2. Span 未关联

症状: Span 之间没有父子关系

原因: Context 传递不正确

解决方案:

// 确保传递正确的 context
func ParentFunction(ctx context.Context) {
ctx, span := tracer.Start(ctx, "parent")
defer span.End()
// ✅ 传递 ctx
ChildFunction(ctx)
}
func ChildFunction(ctx context.Context) {
// ✅ 使用传入的 ctx
ctx, span := tracer.Start(ctx, "child")
defer span.End()
}

3. 内存泄漏

症状: 内存持续增长

原因:

  • 没有调用 span.End()
  • 队列积压

解决方案:

// 1. 始终使用 defer
func Operation(ctx context.Context) {
ctx, span := tracer.Start(ctx, "operation")
defer span.End() // ✅ 使用 defer
}
// 2. 调整队列大小
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter,
sdktrace.WithMaxQueueSize(1024), // 减小队列
sdktrace.WithBatchTimeout(2*time.Second), // 更频繁导出
),
)

4. 高 CPU 使用率

原因: 采样率过高或批量配置不当

解决方案:

# 降低采样率
export OTEL_TRACES_SAMPLER_ARG=0.01 # 1%
# 调整批量配置
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=256
export OTEL_BSP_SCHEDULE_DELAY=10000 # 10 秒

调试技巧

1. 启用详细日志

import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
)
// 设置全局错误处理器
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
log.Printf("OpenTelemetry error: %v", err)
}))

2. 使用 Console Exporter

import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
exporter, _ := stdouttrace.New(
stdouttrace.WithPrettyPrint(),
stdouttrace.WithWriter(os.Stderr),
)

3. 检查 Span 内容

// 自定义 Processor 打印 Span
type DebugProcessor struct{}
func (p *DebugProcessor) OnStart(parent context.Context, s sdktrace.ReadWriteSpan) {
log.Printf("Span started: %s", s.Name())
}
func (p *DebugProcessor) OnEnd(s sdktrace.ReadOnlySpan) {
log.Printf("Span ended: %s, Duration: %v", s.Name(), s.EndTime().Sub(s.StartTime()))
}
func (p *DebugProcessor) Shutdown(ctx context.Context) error { return nil }
func (p *DebugProcessor) ForceFlush(ctx context.Context) error { return nil }
// 添加到 Provider
tp := sdktrace.NewTracerProvider(
sdktrace.WithSpanProcessor(&DebugProcessor{}),
)

4. 性能分析

import _ "net/http/pprof"
// 启动 pprof 服务器
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// 访问 http://localhost:6060/debug/pprof/
// 查看内存和 CPU 使用情况

环境变量完整示例

# 开发环境 - 完整调试
export OTEL_LOG_LEVEL=debug
export OTEL_SERVICE_NAME=my-service-dev
export OTEL_TRACES_SAMPLER=always_on
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_TRACES_EXPORTER=otlp,console
# 生产环境 - 优化性能
export OTEL_LOG_LEVEL=warn
export OTEL_SERVICE_NAME=my-service
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production
export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
export OTEL_EXPORTER_OTLP_COMPRESSION=gzip
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
export OTEL_BSP_MAX_QUEUE_SIZE=2048
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512
export OTEL_BSP_SCHEDULE_DELAY=5000

下一步

参考资源

在Gitlab上编辑本文档