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

本文以社区常用的ORM工具——GORM为例,演示如何通过插件的方式支持链路追踪。

GORM OpenTelemetry插件

GORM可以通过添加 hook 的方式采集 trace 数据,推荐使用官方的 opentelemetry 插件。

安装依赖

go get gorm.io/plugin/opentelemetry/tracing

使用用法

在初始化 gorm.DB 之后,通过安装插件的方式引入 tracing 和 metrics 。

package main

import(
	"gorm.io/plugin/opentelemetry/tracing"
)

func init(){

	db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	if err := db.Use(tracing.NewPlugin()); err != nil {
		panic(err)
	}
}

如果只想采集 tracing 数据,可以按如下方式配置插件。

package main

import(
	"gorm.io/plugin/opentelemetry/tracing"
)

func init(){

	db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	if err := db.Use(tracing.NewPlugin(tracing.WithoutMetrics())); err != nil {
		panic(err)
	}
}

GORM-Jaeger 完整示例

GROM tracing 上报至 Jaeger 示例。

package main

import (
	"context"
	"log"
	"time"

	"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"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/plugin/opentelemetry/tracing"
)

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

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

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)
		}
	}()

	dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	// 设置tracing插件
	if err := db.Use(tracing.NewPlugin(tracing.WithoutMetrics())); err != nil {
		panic(err)
	}

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

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

// 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 doSomething(ctx context.Context, db *gorm.DB) error {
	type Book struct {
		gorm.Model
		Title string
	}
	// 迁移 schema
	db.AutoMigrate(&Book{})

	// Create
	db.WithContext(ctx).Create(&Book{Title: "《Go语言之路》"})
	// Read
	var book Book
	if err := db.WithContext(ctx).Take(&book).Error; err != nil {
		return err
	}
	// delete
	if err := db.WithContext(ctx).Delete(&book).Error; err != nil {
		return err
	}
	log.Println("done!")
	return nil
}

trace 数据: gorm trace

其他常用库的OTel相关内容


扫码关注微信公众号