[TOC] ## 概述 原生 Jaeger 仅支持将数据持久化到 cassandra 和 elasticsearch 中 ## 启动 jaeger docker `docker run -d -p 6831:6831/udp -p 16686:16686 jaegertracing/all-in-one:latest` ## 安装 jaeger-client-go `go get github.com/uber/jaeger-client-go/...` UI 界面为 127.0.0.1:16686 ## 教程 ### 启动一个 trace ``` func Init(service string) (opentracing.Tracer, io.Closer) { cfg := &config.Configuration{ ServiceName: service, Sampler: &config.SamplerConfig{ Type: "const", //全部采样 Param: 1, //1 开启全部采样,0 关闭全部采样,可通过 环境变量 JAEGER_SAMPLER_PARAM 控制 }, Reporter: &config.ReporterConfig{ LogSpans: true, //LocalAgentHostPort: "127.0.0.1:6831", }, } tracer, closer, err := cfg.NewTracer( config.Logger(log.StdLogger))//log.StdLogger 只要实现日志接口即可 if err != nil { panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err)) } return tracer, closer } //调用 func main(){ //初始化,只能在main 函数中初始化,因为需要 defer tracer, closer := Init("jaeger-demo11") defer closer.Close() //StartspanFromContext创建新span时会用到 opentracing.SetGlobalTracer(tracer) } ``` ### 从全局获取一个 span ``` //[推荐方案]通过 opentracing 即可全局调用 span := opentracing.StartSpan("no_context") defer span.Finish() //等同于 tracer := opentracing.GlobalTracer() parentSpan := tracer.StartSpan("parent") defer parentSpan.Finish() ``` ### 创建子跨度 ``` parentSpan := opentracing.StartSpan("parent") defer parentSpan.Finish() ... childSpan := opentracing.StartSpan("child",opentracing.ChildOf(parentSpan.Context())) defer childSpan.finish() ``` ## 实例 ### 带 context 的追踪 <details> <summary>详情</summary> ``` package main import ( "context" "fmt" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/log" "github.com/uber/jaeger-client-go/config" "io" ) func Init(service string) (opentracing.Tracer, io.Closer) { cfg := &config.Configuration{ ServiceName: service, Sampler: &config.SamplerConfig{ Type: "const", //全部采样 Param: 1, //1 开启全部采样,0 关闭全部采样,可通过 环境变量 JAEGER_SAMPLER_PARAM 控制 }, Reporter: &config.ReporterConfig{ LogSpans: true, //LocalAgentHostPort: "127.0.0.1:6831", }, } tracer, closer, err := cfg.NewTracer( )//log.StdLogger 只要实现日志接口即可 if err != nil { panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err)) } return tracer, closer } func main() { tracer, closer := Init("hello-world33") defer closer.Close() opentracing.SetGlobalTracer(tracer) helloTo := "helo" span := tracer.StartSpan("say-hello") span.SetTag("hello-to", helloTo) defer span.Finish() ctx := opentracing.ContextWithSpan(context.Background(), span) ctx2 := formatString(ctx, helloTo) printHello(ctx2,"word") } func formatString(ctx context.Context, helloTo string) context.Context { span, ctx := opentracing.StartSpanFromContext(ctx, "formatString") defer span.Finish() helloStr := fmt.Sprintf("Hello, %s!", helloTo) span.LogFields( log.String("event", "string-format"), log.String("value", helloStr), ) return ctx } func printHello(ctx context.Context, helloStr string) { span, _ := opentracing.StartSpanFromContext(ctx, "printHello") defer span.Finish() println(helloStr) span.LogKV("event", "println") } ``` </details> <br /> ![7A036E37-62F8-4C68-8D68-487E78A76619.png](http://yanxuan.nosdn.127.net/23889f64ede3e0b3ff91055b493dff25.png) ### 以 span 追踪 <details> <summary>详情</summary> ``` package main import ( "fmt" "github.com/opentracing/opentracing-go" "github.com/uber/jaeger-client-go/config" "github.com/uber/jaeger-client-go/log" "io" "time" ) func Init(service string) (opentracing.Tracer, io.Closer) { cfg := &config.Configuration{ ServiceName: service, Sampler: &config.SamplerConfig{ Type: "const", //全部采样 Param: 1, //1 开启全部采样,0 关闭全部采样,可通过 环境变量 JAEGER_SAMPLER_PARAM 控制 }, Reporter: &config.ReporterConfig{ LogSpans: true, //LocalAgentHostPort: "127.0.0.1:6831", }, } tracer, closer, err := cfg.NewTracer( config.Logger(log.StdLogger))//log.StdLogger 只要实现日志接口即可 if err != nil { panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err)) } return tracer, closer } func main() { //初始化,只能在main 函数中初始化,因为需要 defer tracer, closer := Init("jaeger-demo11") defer closer.Close() //StartspanFromContext创建新span时会用到 opentracing.SetGlobalTracer(tracer) parentSpan := opentracing.StartSpan("parent") defer parentSpan.Finish() time.Sleep(10*time.Millisecond) //创建 child span childSpan(parentSpan) } func childSpan(span opentracing.Span) { childSpan := opentracing.StartSpan( "no_context_child_1", opentracing.ChildOf(span.Context()), ) defer childSpan.Finish() time.Sleep(10*time.Millisecond) childspan2(childSpan) } func childspan2(span opentracing.Span) { childSpan := opentracing.StartSpan( "no_context_child_2", opentracing.ChildOf(span.Context()), ) defer childSpan.Finish() time.Sleep(10*time.Millisecond) } ``` </details> <br /> ![B1D169E8-E86D-421F-A9C8-882F6B1D04D8.png](http://yanxuan.nosdn.127.net/b22627a3e4defaabc8c882d9f79d11fd.png) ### http 形式访问 <details> <summary>client.go</summary> ``` package main import ( "fmt" "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "github.com/uber/jaeger-client-go/config" "github.com/uber/jaeger-client-go/log" "io" "net/http" ) func Init(service string) (opentracing.Tracer, io.Closer) { cfg := &config.Configuration{ ServiceName: service, Sampler: &config.SamplerConfig{ Type: "const", //全部采样 Param: 1, //1 开启全部采样,0 关闭全部采样,可通过 环境变量 JAEGER_SAMPLER_PARAM 控制 }, Reporter: &config.ReporterConfig{ LogSpans: true, //LocalAgentHostPort: "127.0.0.1:6831", }, } tracer, closer, err := cfg.NewTracer( config.Logger(log.StdLogger))//log.StdLogger 只要实现日志接口即可 if err != nil { panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err)) } return tracer, closer } func main() { //初始化,只能在main 函数中初始化,因为需要 defer tracer, closer := Init("jaeger-demo11") defer closer.Close() //StartspanFromContext创建新span时会用到 opentracing.SetGlobalTracer(tracer) clientSpan := opentracing.StartSpan("parent2") defer clientSpan.Finish() url := "http://localhost:8082/publish" req, _ := http.NewRequest("GET", url, nil) ext.HTTPUrl.Set(clientSpan, url) ext.HTTPMethod.Set(clientSpan, "GET") // Inject the client span context into the headers tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) resp, _ := http.DefaultClient.Do(req) fmt.Printf("%+v\n", resp.Header) } ``` </details> <br /> <details> <summary>server.go</summary> ``` package main import ( "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "log" "net/http" ) func main() { tracer := opentracing.GlobalTracer() http.HandleFunc("/publish", func(w http.ResponseWriter, r *http.Request) { // 从标题中提取上下文 spanCtx, _ := tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)) serverSpan := tracer.StartSpan("server", ext.RPCServerOption(spanCtx)) defer serverSpan.Finish() }) log.Fatal(http.ListenAndServe(":8082", nil)) } ``` </details> <br /> ![7162E9CB-1465-4E0B-ABC7-7880A24F8157.png](http://yanxuan.nosdn.127.net/0b8614fa904d3ae7e5060323a70856ee.png) ### 跟踪RPC请求 使用Inject和在进程之间传递上下文Extract 为了继续跟踪过程边界和RPC调用,我们需要一种在线路上传播span上下文的方法。OpenTracing API在Tracer界面中提供了两个功能,分别是`Inject(spanContext, format, carrier)`和`Extract(format, carrier)` 该`format`参数引用OpenTracing API定义的三种标准编码之一: * TextMap,其中跨度上下文被编码为字符串键值对的集合, * 将span上下文编码为不透明字节数组的二进制文件, * HTTPHeaders,类似于TextMap,不同之处在于密钥必须安全才能用作HTTP头 这`carrier`是对基础RPC框架的抽象。例如,TextMap格式的载体是一个接口,该接口允许跟踪程序通过`Set(key, value)`函数写入键-值对,而Binary格式的载体只是一个`io.Writer`。 跟踪工具使用`Inject`和`Extract`通过RPC调用传递span上下文 ``` // Inject 注入 实例 carrier := opentracing.HTTPHeadersCarrier(httpReq.Header) err := tracer.Inject( span.Context(), opentracing.HTTPHeaders, carrier) // Extract 提取实例 carrier := opentracing.HTTPHeadersCarrier(httpReq.Header) clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier) var serverSpan opentracing.Span if err == nil { span = tracer.StartSpan( rpcMethodName, ext.RPCServerOption(clientContext)) } else { span = tracer.StartSpan(rpcMethodName) } ``` ### rpcx 调用 [通过 conetxt ] [rpcx_jaeger [master] ](https://github.com/idcpj/rpcx_jaeger) 核心代码,实现与 zipkin 还是稍有不同 ``` func GenerateSpanWithContext(ctx context.Context, operationName string) (opentracing.Span, context.Context, error) { md := ctx.Value(share.ReqMetaDataKey) // share.ReqMetaDataKey 固定值 "__req_metadata" 可自定义 var span opentracing.Span tracer := opentracing.GlobalTracer() if md != nil { carrier := opentracing.TextMapCarrier(md.(map[string]string)) spanContext, err := tracer.Extract(opentracing.TextMap, carrier) if err != nil && err != opentracing.ErrSpanContextNotFound { log.Printf("metadata error %s\n", err) return nil, nil, err } span = tracer.StartSpan(operationName, ext.RPCServerOption(spanContext)) } else { span = opentracing.StartSpan(operationName) } metadata := opentracing.TextMapCarrier(make(map[string]string)) err := tracer.Inject(span.Context(), opentracing.TextMap, metadata) if err != nil { return nil, nil, err } //把metdata 携带的 traceid,spanid,parentSpanid 放入 context ctx = context.WithValue(context.Background(), share.ReqMetaDataKey, (map[string]string)(metadata)) return span, ctx, nil } ``` ### rpcx 调用[通过传递 string(tranid,spanid,parentSpanId)] [rpcx_jaeger [tracing_string] ](https://github.com/idcpj/rpcx_jaeger/tree/tracing_string) 核心代码 ``` /** 改函数只适用于 jaeger 通过传递 tracekey string 来追踪 @tracekey 格式 : 6f8b8a1101b0124f:6f8b8a1101b0124f:0000000000000000:1 首个 span span, carrier, _ := lib.GenerateSpanWithContext( "first2","") defer span.Finish() 之后 通过传递 carrier 来实现追踪 span, _, _ := lib.GenerateSpanWithContext( "first2",carrier) defer span.Finish() */ func GenerateSpanWithContext(operationName string, traceKey string) (opentracing.Span, string, error) { var span opentracing.Span tracer := opentracing.GlobalTracer() if traceKey != "" { spanContext, err := jaeger.ContextFromString(traceKey) //通过 traceKey 获取 spanContext if err != nil { log.Printf("metadata error %s\n", err) return nil, "", err } span = tracer.StartSpan(operationName, ext.RPCServerOption(spanContext)) } else { span = opentracing.StartSpan(operationName) } metadata := opentracing.TextMapCarrier(make(map[string]string)) err := tracer.Inject(span.Context(), opentracing.TextMap, metadata) if err != nil { return nil, "", err } return span, metadata[jaeger.TraceContextHeaderName], nil } ```