1.gorm 插件实现链路追踪 //通过 github.com/uptrace/opentelemetry-go-extra/otelgorm库可实现mysql-grom链路追踪
//
if o.enableTrace {
err = db.Use(otelgorm.NewPlugin())
if err != nil {
return nil, fmt.Errorf("using gorm opentelemetry, err: %v", err)
}
}
// gorm 插件demo learnku.com/docs/gorm/v2/write_plugins/9750#c5a38b 插件实现描述下: 当用户通过 db.Use(plugin)
注册插件时,GORM 会调用插件的 Initialize
方法,插件在此方法中完成核心功能的注册(如绑定回调、修改配置等)。
以after回调为例子

2.go-redis 实现的链路追踪 基于当前库: github.com/redis/go-redis/extra/redisotel/v9
// tracerProvider *trace.TracerProvider
rdb
if o.tracerProvider != nil {
err = redisotel.InstrumentTracing(rdb, redisotel.WithTracerProvider(o.tracerProvider))
if err != nil {
return nil, err
}
}
3.grpc实现拦截追踪
scss
func (s *grpcServer) unaryServerOptions() grpc.ServerOption {
unaryServerInterceptors := []grpc.UnaryServerInterceptor{
interceptor.UnaryServerRecovery(),
interceptor.UnaryServerRequestID(),
}
....
// trace interceptor
if config.Get().App.EnableTrace {
streamServerInterceptors = append(streamServerInterceptors, interceptor.StreamServerTracing())
}
return grpc.ChainStreamInterceptor(streamServerInterceptors...)
4.http基于trace中间件处理 提取上游追踪上下文 使用cfg.Propagators.Extract()从HTTP请求头中提取上游服务传递过来的追踪上下文
创建或继续span 调用tracer.Start(ctx, spanName, tOpts...),这里的关键是:
如果提取到了上游追踪上下文,会继续现有的trace,创建子span 如果没有提取到上游上下文,才会创建新的trace
scss
return func(c *gin.Context) {
c.Set(tracerKey, tracer)
savedCtx := c.Request.Context()
defer func() {
c.Request = c.Request.WithContext(savedCtx)
}()
ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header))
route := c.FullPath()
tOpts := []oteltrace.SpanStartOption{
oteltrace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", c.Request)...),
oteltrace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(c.Request)...),
oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(serviceName, route, c.Request)...),
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
}
spanName := route
if spanName == "" {
spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method)
}
ctx, span := tracer.Start(ctx, spanName, tOpts...)
defer span.End()
// pass the span through the request context
c.Request = c.Request.WithContext(ctx)
// serve the request to the next interceptor
c.Next()
status := c.Writer.Status()
attrs := semconv.HTTPAttributesFromHTTPStatusCode(status)
spanStatus, spanMessage := semconv.SpanStatusFromHTTPStatusCode(status)
span.SetAttributes(attrs...)
span.SetStatus(spanStatus, spanMessage)
if len(c.Errors) > 0 {
span.SetAttributes(attribute.String("gin.errors", c.Errors.String()))
}
}
基于gozero的简单封装 propagation.MapCarrier适合mq之间,kv传递 propagation.HeaderCarrier适合http、rpc之间,kv传递
kratos 链路追踪实现: juejin.cn/post/696828...
// go-zero :zhuanlan.zhihu.com/p/591180238
go
package telemetry
import (
"context"
"os"
"sync"
zeroTrace "github.com/zeromicro/go-zero/core/trace"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
"go.opentelemetry.io/otel/trace"
)
var (
resourcesOnce sync.Once
)
// https://zhuanlan.zhihu.com/p/591180238
// https://developer.aliyun.com/article/1597831
// 前提-gozero已经提前完成初始化操作
// TracingIt 给耗时的操作加上链路追踪。特别是需要调用外部服务的时候使用
// kind取值参考:trace.SpanKindProducer,上游决定是生产端,消费端,还是内部调用
func TracingIt(ctx context.Context, operation string, kind trace.SpanKind, fn func(ctx context.Context, span trace.Span) error, attrs ...attribute.KeyValue) error {
tracer := otel.GetTracerProvider().Tracer(zeroTrace.TraceName)
newCtx, span := tracer.Start(ctx, operation, trace.WithSpanKind(kind), trace.WithAttributes(attrs...))
defer span.End()
err := fn(newCtx, span)
setSpanStatus(span, err)
return err
}
// setSpanStatus 统一设置span状态
func setSpanStatus(span trace.Span, err error) {
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
} else {
span.SetStatus(codes.Ok, "ok")
}
}
// NewSpan 创建一个新的span,支持自定义traceName和spanKind,这种方式记得defer span.End()
func NewSpan(ctx context.Context, operation string, opts ...SpanOption) (newCtx context.Context, span trace.Span) {
options := defaultSpanOptions()
for _, opt := range opts {
opt(&options)
}
tracer := otel.GetTracerProvider().Tracer(options.traceName)
return tracer.Start(ctx, operation, trace.WithSpanKind(options.spanKind), trace.WithAttributes(options.attrs...))
}
// SpanOption 用于自定义span参数
type SpanOption func(*spanOptions)
type spanOptions struct {
traceName string
spanKind trace.SpanKind
attrs []attribute.KeyValue
}
func defaultSpanOptions() spanOptions {
return spanOptions{
traceName: zeroTrace.TraceName,
spanKind: trace.SpanKindProducer,
attrs: nil,
}
}
func WithTraceName(name string) SpanOption {
return func(o *spanOptions) {
o.traceName = name
}
}
func WithSpanKind(kind trace.SpanKind) SpanOption {
return func(o *spanOptions) {
o.spanKind = kind
}
}
func WithAttributes(attrs ...attribute.KeyValue) SpanOption {
return func(o *spanOptions) {
o.attrs = attrs
}
}
// InjectMapContext 将追踪上下文注入到 MapCarrier 中,支持自定义Propagator
func InjectMapContext(spanCtx context.Context, propagators ...propagation.TextMapPropagator) *propagation.MapCarrier {
carrier := &propagation.MapCarrier{}
propagator := otel.GetTextMapPropagator()
if len(propagators) > 0 {
propagator = propagators[0]
}
propagator.Inject(spanCtx, carrier)
return carrier
}
// ExtractMapContext 从 MapCarrier 中提取追踪上下文,支持自定义Propagator
func ExtractMapContext(carrier propagation.MapCarrier, propagators ...propagation.TextMapPropagator) context.Context {
propagator := otel.GetTextMapPropagator()
if len(propagators) > 0 {
propagator = propagators[0]
}
return propagator.Extract(context.Background(), carrier)
}
// InjectHeaderContext 将追踪上下文注入到 HeaderCarrier 中,支持自定义Propagator
func InjectHeaderContext(spanCtx context.Context, propagators ...propagation.TextMapPropagator) *propagation.HeaderCarrier {
carrier := &propagation.HeaderCarrier{}
propagator := otel.GetTextMapPropagator()
if len(propagators) > 0 {
propagator = propagators[0]
}
propagator.Inject(spanCtx, carrier)
return carrier
}
// ExtractHeaderContext 从 HeaderCarrier 中提取追踪上下文,支持自定义Propagator
func ExtractHeaderContext(carrier propagation.HeaderCarrier, propagators ...propagation.TextMapPropagator) context.Context {
propagator := otel.GetTextMapPropagator()
if len(propagators) > 0 {
propagator = propagators[0]
}
return propagator.Extract(context.Background(), carrier)
}
// AddResources 添加一些链路需要的字段,只初始化一次
func AddResources(serviceName string) {
resourcesOnce.Do(func() {
instanceId := os.Getenv("INSTANCE_ID")
if instanceId == "" {
instanceId = os.Getenv("POD_NAME")
}
if instanceId == "" {
instanceId = serviceName
}
if instanceId == "" {
instanceId = "unknown-instance"
}
zeroTrace.AddResources(semconv.ServiceInstanceID(instanceId))
zeroTrace.AddResources(semconv.ServiceName(serviceName))
})
}