Golang中关于gokit和tracing的问题探讨

Golang中关于gokit和tracing的问题探讨 关于 #gokit 和 #tracing 的问题。

当我在 go kit 中使用 #opentracing 时,在 Zipkin 或 Jaeger 中只添加了一个跨度并记录了整个请求的持续时间,但我希望每个函数都有一个跨度。有人在 Gokit 和追踪方面有经验吗?

1 回复

更多关于Golang中关于gokit和tracing的问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go kit中实现函数级别的追踪需要手动创建子跨度。默认情况下,Go kit的端点中间件只会创建一个顶层跨度,但你可以通过以下方式为每个函数创建独立的跨度:

import (
    "context"
    "github.com/go-kit/kit/endpoint"
    "github.com/opentracing/opentracing-go"
    "github.com/opentracing/opentracing-go/ext"
)

// 创建追踪中间件
func TracingMiddleware(tracer opentracing.Tracer, operationName string) endpoint.Middleware {
    return func(next endpoint.Endpoint) endpoint.Endpoint {
        return func(ctx context.Context, request interface{}) (interface{}, error) {
            // 从上下文获取父跨度或创建新跨度
            var span opentracing.Span
            if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil {
                span = tracer.StartSpan(operationName, opentracing.ChildOf(parentSpan.Context()))
            } else {
                span = tracer.StartSpan(operationName)
            }
            defer span.Finish()
            
            // 设置标签
            ext.SpanKindRPCClient.Set(span)
            ext.Component.Set(span, "gokit-service")
            
            // 将跨度放入上下文
            ctx = opentracing.ContextWithSpan(ctx, span)
            
            // 执行端点
            response, err := next(ctx, request)
            
            // 记录错误
            if err != nil {
                ext.Error.Set(span, true)
                span.LogKV("error", err.Error())
            }
            
            return response, err
        }
    }
}

// 业务函数包装器
func TraceFunction(tracer opentracing.Tracer, funcName string, fn func(context.Context) error) func(context.Context) error {
    return func(ctx context.Context) error {
        span, ctx := opentracing.StartSpanFromContext(ctx, funcName)
        defer span.Finish()
        
        // 执行函数
        err := fn(ctx)
        if err != nil {
            span.LogKV("error", err.Error())
        }
        return err
    }
}

// 使用示例
func makeEndpoint(svc Service, tracer opentracing.Tracer) endpoint.Endpoint {
    return func(ctx context.Context, request interface{}) (interface{}, error) {
        // 为每个业务函数创建独立跨度
        err := TraceFunction(tracer, "validate_request", svc.Validate)(ctx)
        if err != nil {
            return nil, err
        }
        
        err = TraceFunction(tracer, "process_data", svc.Process)(ctx)
        if err != nil {
            return nil, err
        }
        
        return TraceFunction(tracer, "generate_response", svc.Generate)(ctx)
    }
}

// 在服务构建时应用中间件
func NewService(tracer opentracing.Tracer) Service {
    var svc Service
    svc = serviceImpl{}
    
    // 为端点添加追踪
    endpoint := makeEndpoint(svc, tracer)
    endpoint = TracingMiddleware(tracer, "api_endpoint")(endpoint)
    
    return svc
}

对于HTTP传输层,可以添加额外的中间件:

import (
    "net/http"
    "github.com/go-kit/kit/transport/http"
    "github.com/opentracing/opentracing-go"
    "github.com/uber/jaeger-client-go"
)

func HTTPTracingMiddleware(tracer opentracing.Tracer) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // 从HTTP头部提取追踪上下文
            wireContext, _ := tracer.Extract(
                opentracing.HTTPHeaders,
                opentracing.HTTPHeadersCarrier(r.Header),
            )
            
            // 创建服务器跨度
            serverSpan := tracer.StartSpan(
                r.URL.Path,
                ext.RPCServerOption(wireContext),
                opentracing.Tag{Key: string(ext.Component), Value: "gokit-http"},
                opentracing.Tag{Key: "http.method", Value: r.Method},
                opentracing.Tag{Key: "http.url", Value: r.URL.Path},
            )
            defer serverSpan.Finish()
            
            // 将跨度放入上下文
            ctx := opentracing.ContextWithSpan(r.Context(), serverSpan)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

配置Jaeger示例:

import (
    "io"
    "github.com/opentracing/opentracing-go"
    "github.com/uber/jaeger-client-go/config"
)

func InitTracing(serviceName string) (opentracing.Tracer, io.Closer) {
    cfg := &config.Configuration{
        ServiceName: serviceName,
        Sampler: &config.SamplerConfig{
            Type:  "const",
            Param: 1,
        },
        Reporter: &config.ReporterConfig{
            LogSpans: true,
        },
    }
    
    tracer, closer, err := cfg.NewTracer()
    if err != nil {
        panic(err)
    }
    
    opentracing.SetGlobalTracer(tracer)
    return tracer, closer
}

这种方式会在Jaeger/Zipkin中显示完整的调用链,每个函数都有独立的跨度,包括父子关系和时间线。

回到顶部