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中显示完整的调用链,每个函数都有独立的跨度,包括父子关系和时间线。

