GORM配置链路追踪
承蒙大家厚爱,我的《Go语言之路》的纸质版图书已经上架京东,有需要的朋友请点击 此链接 购买。
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 数据: