Open-Telemetry的第三方软件包合集 包括了多个社区中常用库的OpenTelemetry支持。随着 OpenTelemetry的不断迭代,相信整个链路追踪的生态也会越发完善。

本文以社区常用的Redis库——go-redis为例,演示如何通过插件的方式支持链路追踪。

go-redis 教程可查看我之前的博客:Go语言操作Redis

redisotel

go-redis 官方提供了 OTel 插件——redisotel

安装依赖

这里下载的是v9版本,请确保下载的 redisotel 版本与你使用的go-redis版本一致。

go get github.com/redis/go-redis/extra/redisotel/v9

用法

通过添加一个 hook 来启用链路追踪:

import (
    "github.com/redis/go-redis/v9"
    "github.com/redis/go-redis/extra/redisotel/v9"
)

rdb := rdb.NewClient(&rdb.Options{...})

// 启用 tracing
if err := redisotel.InstrumentTracing(rdb); err != nil {
	panic(err)
}

go-redis Jaeger完整示例

redis tracing 上报至 Jaeger 示例。

package main

import (
	"context"
	"log"
	"sync"
	"time"

	"github.com/redis/go-redis/extra/redisotel/v9"
	"github.com/redis/go-redis/v9"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

const (
	serviceName    = "redis-Jaeger-Demo"
	jaegerEndpoint = "127.0.0.1:4318"
)

var tracer = otel.Tracer("redis-demo")

// newJaegerTraceProvider 创建一个 Jaeger Trace Provider
func newJaegerTraceProvider(ctx context.Context) (*sdktrace.TracerProvider, error) {
	// 创建一个使用 HTTP 协议连接本机Jaeger的 Exporter
	exp, err := otlptracehttp.New(ctx,
		otlptracehttp.WithEndpoint(jaegerEndpoint),
		otlptracehttp.WithInsecure())
	if err != nil {
		return nil, err
	}
	res, err := resource.New(ctx, resource.WithAttributes(semconv.ServiceName(serviceName)))
	if err != nil {
		return nil, err
	}
	traceProvider := sdktrace.NewTracerProvider(
		sdktrace.WithResource(res),
		sdktrace.WithSampler(sdktrace.AlwaysSample()), // 采样
		sdktrace.WithBatcher(exp, sdktrace.WithBatchTimeout(time.Second)),
	)
	return traceProvider, nil
}

// initTracer 初始化 Tracer
func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
	tp, err := newJaegerTraceProvider(ctx)
	if err != nil {
		return nil, err
	}

	otel.SetTracerProvider(tp)
	otel.SetTextMapPropagator(
		propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}),
	)
	return tp, nil
}

func main() {
	ctx := context.Background()

	tp, err := initTracer(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer func() {
		if err := tp.Shutdown(ctx); err != nil {
			log.Printf("Error shutting down tracer provider: %v", err)
		}
	}()

	rdb := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
	})

	// 启用 tracing
	if err := redisotel.InstrumentTracing(rdb); err != nil {
		panic(err)
	}

	// 启用 metrics
	if err := redisotel.InstrumentMetrics(rdb); err != nil {
		panic(err)
	}

	ctx, span := tracer.Start(ctx, "doSomething")
	defer span.End()

	if err := doSomething(ctx, rdb); err != nil {
		span.RecordError(err) // 记录error
		span.SetStatus(codes.Error, err.Error())
	}
}

func doSomething(ctx context.Context, rdb *redis.Client) error {
	if err := rdb.Set(ctx, "name", "Q1mi", time.Minute).Err(); err != nil {
		return err
	}
	if err := rdb.Set(ctx, "tag", "OTel", time.Minute).Err(); err != nil {
		return err
	}
	var wg sync.WaitGroup
	for range 10 {
		wg.Add(1)
		go func() {
			defer wg.Done()
			val := rdb.Get(ctx, "tag").Val()
			if val != "OTel" {
				log.Printf("%q != %q", val, "OTel")
			}
		}()
	}
	wg.Wait()

	if err := rdb.Del(ctx, "name").Err(); err != nil {
		return err
	}
	if err := rdb.Del(ctx, "tag").Err(); err != nil {
		return err
	}
	log.Println("done!")
	return nil
}

trace 数据: go-redis trace

其他常用库的OTel相关内容


扫码关注微信公众号