官网:
链路追踪有很多概念:collector、OTLP、监控、指标、采样等。
1、安装 jaeger
jaeger 是链路追踪的 UI 界面,otel 不提供可视化界面,所以需要先安装 jaeger。
docker run -d --rm --name jaeger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
jaegertracing/all-in-one:latest
运行后访问:http://localhost:16686,这是 jaeger 控制台页面
4317:OTLP 的 GRPC 访问端口
4318:OTLP 的 HTTP 访问端口
这两个端口在代码上需要使用
2、Gin 集成 otel
2.1、导入依赖
jaeger 集成的主要包:https://opentelemetry.io/docs/languages/go/exporters/#otlp-traces-over-http
go get "go.opentelemetry.io/otel" \
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" \
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace" \
"go.opentelemetry.io/otel/propagation" \
"go.opentelemetry.io/otel/sdk/metric" \
"go.opentelemetry.io/otel/sdk/resource" \
"go.opentelemetry.io/otel/sdk/trace" \
"go.opentelemetry.io/otel/semconv/v1.24.0" \
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
# gin 集成包
go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin
# jaeger 集成包
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
2.2、全部代码
1、 创建 opentelemetry 包,该内容来自官网入门,唯一不同的是:newTraceProvider(),该方法集成了 jaeger 方能在 UI 界面中查看。
package opentelemetry
import (
"context"
"errors"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"time"
)
// SetupOTelSDK setupOTelSDK bootstraps the OpenTelemetry pipeline.
// If it does not return an error, make sure to call shutdown for proper cleanup.
func SetupOTelSDK(ctx context.Context, serviceName, serviceVersion string) (shutdown func(context.Context) error, err error) {
var shutdownFuncs []func(context.Context) error
// shutdown calls cleanup functions registered via shutdownFuncs.
// The errors from the calls are joined.
// Each registered cleanup will be invoked once.
shutdown = func(ctx context.Context) error {
var err error
for _, fn := range shutdownFuncs {
err = errors.Join(err, fn(ctx))
}
shutdownFuncs = nil
return err
}
// handleErr calls shutdown for cleanup and makes sure that all errors are returned.
handleErr := func(inErr error) {
err = errors.Join(inErr, shutdown(ctx))
}
// Set up resource.
res, err := newResource(serviceName, serviceVersion)
if err != nil {
handleErr(err)
return
}
// Set up propagator.
prop := newPropagator()
otel.SetTextMapPropagator(prop)
// Set up trace provider.
tracerProvider, err := newTraceProvider(res)
if err != nil {
handleErr(err)
return
}
shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
otel.SetTracerProvider(tracerProvider)
// Set up meter provider.
meterProvider, err := newMeterProvider(res)
if err != nil {
handleErr(err)
return
}
shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)
otel.SetMeterProvider(meterProvider)
return
}
func newResource(serviceName, serviceVersion string) (resource.Resource, error) {
return resource.Merge(resource.Default(),
resource.NewWithAttributes(semconv.SchemaURL,
semconv.ServiceName(serviceName),
semconv.ServiceVersion(serviceVersion),
))
}
func newPropagator() propagation.TextMapPropagator {
return propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
)
}
func newTraceProvider(res resource.Resource) (trace.TracerProvider, error) {
// 集成 jaeger 追踪
exp, err := otlptracehttp.New(
context.Background(),
otlptracehttp.WithEndpointURL("http://10.10.200.60:4318/v1/traces"))
if err != nil {
return nil, err
}
traceProvider := trace.NewTracerProvider(
trace.WithBatcher(exp,
// Default is 5s. Set to 1s for demonstrative purposes.
trace.WithBatchTimeout(time.Second)),
trace.WithResource(res),
)
return traceProvider, nil
}
func newMeterProvider(res resource.Resource) (metric.MeterProvider, error) {
metricExporter, err := stdoutmetric.New()
if err != nil {
return nil, err
}
meterProvider := metric.NewMeterProvider(
metric.WithResource(res),
metric.WithReader(metric.NewPeriodicReader(metricExporter,
// Default is 1m. Set to 3s for demonstrative purposes.
metric.WithInterval(3time.Second))),
)
return meterProvider, nil
}
2、集成 Gin,这里需要用到 otelgin 包,丢给 Middleware 即可。
package main
import (
"context"
"errors"
"fmt"
"github.com/gin-gonic/gin"
opentelemetry "go-test/pkg/open_telemetry"
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
"log"
"log/slog"
"net/http"
)
func main() {
if err := run(); err != nil {
panic(err)
}
}
func run() error {
slog.Info("服务启动...")
// 设置 otel SDK 基本配置
otelShutdown, err := opentelemetry.SetupOTelSDK(context.Background(), "juzipi", "v1.0.0")
if err != nil {
return err
}
defer func() {
err = errors.Join(err, otelShutdown(context.Background()))
}()
// 设置 Gin 的开发环境
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
// 嵌入 OpenTelemetry 集成 gin
r.Use(otelgin.Middleware("go-test"))
api := r.Group("/api")
api.GET("/", func(ctx *gin.Context) {
fmt.Println("forward to service 1")
ctx.String(http.StatusOK, "forward to service 1")
})
api.GET("/login", func(ctx *gin.Context) {
panic(errors.New("一个错误"))
})
r.Run(":8100")
slog.Info("服务关闭...")
return nil
}
3、运行 go 程序,分别打开访问
http://localhost:8100/api/login
最后打开 jaeger UI 界面,刷新看看Service服务中是否有你配置的名字,选择完后点击 Find Traces,右侧即可查看刚才访问的路由信息。