集成opentracing

145 阅读8分钟

进程内/http/grpc携带trace信息

进程内通过ctx携带traceId

package main

import (
    "context"
    "fmt"
    "go.etcd.io/etcd/pkg/v3/stringutil"
    "log"
    "runtime"
    "strings"
    "time"
)

func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, "trace_id", stringutil.RandString(20))
    ctx = context.WithValue(ctx, "user_id", 2)
    var msg string = visitWeb(ctx)
    fmt.Println(msg)
}

func visitWeb(ctx context.Context) string {
    begin := time.Now()
    userId := ctx.Value("user_id").(int)
    traceId := ctx.Value("trace_id").(string)
    defer func() {
       pc, _, _, _ := runtime.Caller(0)
       funcName := runtime.FuncForPC(pc).Name()
       log.Printf("func: %s, begin: %d, end: %d, userId: %d, traceId: %s", funcName, begin.Nanosecond(), time.Since(begin).Nanoseconds(), userId, traceId)
    }()
    go recordUV(ctx)
    var content string = getRecommond(ctx)
    return content
}

func recordUV(ctx context.Context) {
    begin := time.Now()
    userId := ctx.Value("user_id").(int)
    traceId := ctx.Value("trace_id").(string)
    defer func() {
       pc, _, _, _ := runtime.Caller(0)
       funcName := runtime.FuncForPC(pc).Name()
       log.Printf("func: %s, begin: %d, end: %d, userId: %d, traceId: %s", funcName, begin.Nanosecond(), time.Since(begin).Nanoseconds(), userId, traceId)
    }()

    time.Sleep(time.Millisecond * 3)
}

func getRecommond(ctx context.Context) string {
    begin := time.Now()
    userId := ctx.Value("user_id").(int)
    traceId := ctx.Value("trace_id").(string)
    defer func() {
       pc, _, _, _ := runtime.Caller(0)
       funcName := runtime.FuncForPC(pc).Name()
       log.Printf("func: %s, begin: %d, end: %d, userId: %d, traceId: %s", funcName, begin.Nanosecond(), time.Since(begin).Nanoseconds(), userId, traceId)
    }()

    var role string = getUserRole(ctx)
    content := make([]string, 0)
    if strings.ToLower(role) != "vip" {
       content = append(content, "广告")
    }
    content = append(content, "grpc")
    content = append(content, "gorm")
    return strings.Join(content, "\n")
}

func getUserRole(ctx context.Context) string {
    begin := time.Now()
    userId := ctx.Value("user_id").(int)
    traceId := ctx.Value("trace_id").(string)
    defer func() {
       pc, _, _, _ := runtime.Caller(0)
       funcName := runtime.FuncForPC(pc).Name()
       log.Printf("func: %s, begin: %d, end: %d, userId: %d, traceId: %s", funcName, begin.Nanosecond(), time.Since(begin).Nanoseconds(), userId, traceId)
    }()
    return "VIP"
}

//
//2024/08/15 19:47:25 func: main.getUserRole.func1, begin: 679226000, end: 15890, userId: 2, traceId: qHSPs5kzXY35ScUM3puI
//2024/08/15 19:47:25 func: main.getRecommond.func1, begin: 679226000, end: 183109, userId: 2, traceId: qHSPs5kzXY35ScUM3puI
//2024/08/15 19:47:25 func: main.visitWeb.func1, begin: 679219000, end: 194119, userId: 2, traceId: qHSPs5kzXY35ScUM3puI

http通过header携带traceId

  • server/main.go
package main

import (
    "github.com/gin-gonic/gin"
    "log"
    "net/http"
    "time"
)

func main() {
    router := gin.Default()
    router.Use(traceMiddleawre)
    router.GET("/greet", greet)
    router.Run("127.0.0.1:8080")
}

func traceMiddleawre(ctx *gin.Context) {
    begin := time.Now()
    traceId := ctx.GetHeader("trace_id")
    userId := ctx.GetHeader("user_id")
    ctx.Next()
    log.Printf("trace_id %s user_id %s  %s begin time %d use time %d ns\n", traceId, userId, ctx.Request.RequestURI, begin.Nanosecond(), time.Since(begin).Nanoseconds())
}

func greet(ctx *gin.Context) {
    time.Sleep(time.Millisecond * 100)
    ctx.String(http.StatusOK, "Hello World")
}

  • client/main.go
package main

import (
    "context"
    "fmt"
    "go.etcd.io/etcd/pkg/v3/stringutil"
    "io"
    "log"
    "net/http"
    "strconv"
    "time"
)

func main() {
    userId := 8
    ctx := context.Background()
    fmt.Println(greet(ctx, userId))
}

func greet(ctx context.Context, userId int) string {
    url := "http://127.0.0.1:8080/greet"
    begin := time.Now()
    traceId := stringutil.RandString(20)
    defer func() {
       log.Printf("trace_id %s user_id %s %s begin time %d use time %d ns\n", traceId, strconv.Itoa(userId), url, begin.Nanosecond(), time.Since(begin).Nanoseconds())
    }()
    client := &http.Client{}
    request, err := http.NewRequest("GET", url, nil)
    request.Header.Set("trace_id", traceId)
    request.Header.Set("user_id", strconv.Itoa(userId))
    if err != nil {
       log.Fatal(err)
    }
    response, err := client.Do(request)
    if err != nil {
       log.Fatal(err)
    }
    defer response.Body.Close()
    bs, err := io.ReadAll(response.Body)
    if err != nil {
       log.Fatal(err)
    }
    return string(bs)
}

grpc通过meta携带traceId

//服务端
grpc.ChainUnaryInterceptor(tracerInterceptor)
metadata.FromIncomingContext

//客户端
grpc.WithChainUnaryInterceptor(traceInterceptor)
metadata.AppendToOutgoingContext
  • proto/hello.proto
syntax = "proto3";
package helloworld;

option go_package="./;proto";


service Greeter{
  rpc SayHello(HelloReq) returns (HelloRes);
}


message HelloReq{
  string name=1;
}

message HelloRes{
  string msg=1;
}

//protoc --go_out=./proto --go-grpc_out=./proto ./proto/hello.proto
  • server/main.go
package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    pb "grpc-demo/grpc_trace/proto"
    "log"
    "net"
    "time"
)

type server struct {
    pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
    fmt.Println(req.Name)
    return &pb.HelloReply{Message: "Hello " + req.Name}, nil
}

func main() {
    lis, err := net.Listen("tcp", "localhost:8080")
    if err != nil {
       log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer(grpc.ChainUnaryInterceptor(tracerInterceptor))
    pb.RegisterGreeterServer(s, &server{})
    err = s.Serve(lis)
    if err != nil {
       log.Fatalf("failed to serve: %v", err)
    }
}

func tracerInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
    begin := time.Now()
    var traceId string
    if md, exist := metadata.FromIncomingContext(ctx); exist {
       if v, ok := md["traceid"]; ok {
          traceId = v[0]
       }
    }
    res, err := handler(ctx, req)
    log.Printf("trace id: %v, begin %d, end %d", traceId, begin.Nanosecond(), time.Since(begin).Nanoseconds())
    return res, err
}

  • client/main.go
package main

import (
    "context"
    "fmt"
    "go.etcd.io/etcd/pkg/v3/stringutil"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "google.golang.org/grpc/metadata"
    pb "grpc-demo/grpc_trace/proto"
    "log"
    "time"
)

func main() {
    conn, err := grpc.NewClient("localhost:8080",
       grpc.WithTransportCredentials(insecure.NewCredentials()),
       grpc.WithChainUnaryInterceptor(traceInterceptor),
    )

    if err != nil {
       log.Fatal(err)
    }
    client := pb.NewGreeterClient(conn)
    res, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "mmm"})
    if err != nil {
       log.Fatal(err)
    }
    fmt.Println(res.Message)

}

func traceInterceptor(ctx context.Context, method string, req any, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    begin := time.Now()
    traceId := stringutil.RandString(20)
    ctx = metadata.AppendToOutgoingContext(ctx, "traceid", traceId)
    err := invoker(ctx, method, req, reply, cc, opts...)
    log.Printf("traceid: %s, begin: %d,end %d", traceId, begin.Nanosecond(), time.Since(begin).Nanoseconds())
    return err
}

集成opentracing

opentracing主要的interface

docker run --rm --name jaeger \
  -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
  -p 14250:14250 \
  -p 14268:14268 \
  -p 14269:14269 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.60
go get github.com/opentracing/opentracing-go
go get github.com/uber/jaeger-client-go
  • 1.Jaeger: open source, end-to-end distributed tracing Monitor and troubleshoot transactions in complex distributed systems
Jaeger 解决的问题
* distributed transaction monitoring分布式事务监控
* performance and latency optimization性能和延迟优化
* root cause analysis根本原因分析
* service dependency analysis服务依赖分析
* distributed context propagation分布式上下文传播
    1. Tracer 创建span, 传递BaggageItem,
    • pubilc: 其中BaggageItem可以在服务之间传递,是公共的
    • private: 对应的tag和log则是属于该span.
    1. "Span" 描述一个操作一次调用的生命周期,如执行一个函数的时间,一次rpc的时间.

主要接口

type Span interface {
    Finish()
    FinishWithOptions(opts FinishOptions)
    Context() SpanContext
    SetOperationName(operationName string) Span
    SetTag(key string, value interface{}) Span
    LogFields(fields ...log.Field)
    LogKV(alternatingKeyValues ...interface{})
    SetBaggageItem(restrictedKey string, value string) Span
    BaggageItem(restrictedKey string) string
    Tracer() Tracer
    LogEvent(event string)
    LogEventWithPayload(event string, payload interface{})
    Log(data LogData)
}


type Tracer interface {
    StartSpan(operationName string, opts ...StartSpanOption) Span
    Inject(sm SpanContext, format interface{}, carrier interface{}) error
    Extract(format interface{}, carrier interface{}) (SpanContext, error)
}

type SpanContext interface {
    ForeachBaggageItem(handler func(k string, v string) bool)
}

span 两种关系

ChildOf      //父子关系, 父依赖子的返回值
FollowsFrom  //父**不依赖**子的返回

进程之间传递依赖Inject和Extract, Extract负责从Carrier里反序列化出SpanContext, Opentracing实现了3种序列化器

Inject(sm SpanContext, format interface{}, carrier interface{}) error
Extract(format interface{}, carrier interface{}) (SpanContext, error)


Binary       不透明的二进制, Inject使用Carrier符合io.Writer,反序列化符合io.Reader接口
TextMap      <k,v>格式,k,v可以是任意字符串, 序列化时遵守TextMapWriter接口, 反序列化支持TextMapReader接口
HTTPHeaders  <k,v>格式, 只能包含英文,不能包含中文.

image.png

NOTE: 可以获取traceID,SpanID注入到log中.

进程内追踪,集成opentracing

image.png

package main

import (
    "context"
    "fmt"
    "github.com/opentracing/opentracing-go"
    "github.com/opentracing/opentracing-go/log"
    "github.com/uber/jaeger-client-go"
    "github.com/uber/jaeger-client-go/config"
    "github.com/uber/jaeger-lib/metrics"
    "go.etcd.io/etcd/pkg/v3/stringutil"
    "io"
    "strings"
    "time"
)

func main() {
    jaeger, closer, err := NewJaegerTracer("my-svc", "127.0.0.1:6831")
    if err != nil {
       panic(err)
    }
    defer closer.Close()
    opentracing.SetGlobalTracer(jaeger)
    ctx := context.Background()
    var msg string = visitWeb(ctx)
    fmt.Println(msg)
}

func NewJaegerTracer(ServiceName string, jaegerHost string) (opentracing.Tracer, io.Closer, error) {
    configuration := config.Configuration{
       ServiceName: ServiceName,
       Sampler: &config.SamplerConfig{
          Type:  jaeger.SamplerTypeConst,
          Param: 1,
       },
       Reporter: &config.ReporterConfig{
          BufferFlushInterval: 1 * time.Second,
          LogSpans:            true,
          LocalAgentHostPort:  jaegerHost,
       },
    }
    tracer, closer, err := configuration.NewTracer(config.Logger(jaeger.NullLogger), config.Metrics(metrics.NullFactory))
    if err != nil {
       panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
    }
    opentracing.SetGlobalTracer(tracer)
    return tracer, closer, err
}

func visitWeb(ctx context.Context) string {
    span := opentracing.GlobalTracer().StartSpan("visitWeb")
    defer span.Finish()
    time.Sleep(time.Second * 3)
    span.SetTag("tagk1", "tagv1")
    span.LogFields(
       log.Int("user_id", 8),
       log.String("visit_page", "/home"),
    )
    span.SetBaggageItem("trace_id", stringutil.RandString(20))
    span.SetBaggageItem("user_id", "8")
    recordUV(span)
    var content string = getRecommond(span)
    return content
}

func recordUV(parentSpan opentracing.Span) {
    span := opentracing.GlobalTracer().StartSpan("recordUV", opentracing.FollowsFrom(parentSpan.Context()))
    defer span.Finish()
    time.Sleep(time.Second * 3)
}

func getRecommond(parentSpan opentracing.Span) string {
    span := opentracing.GlobalTracer().StartSpan("getRecommond", opentracing.ChildOf(parentSpan.Context()))
    defer span.Finish()
    time.Sleep(time.Second * 2)
    var role string = getUserRole(span)
    content := make([]string, 0)
    if strings.ToLower(role) != "vip" {
       content = append(content, "广告")
    }
    content = append(content, "grpc")
    content = append(content, "gorm")
    return strings.Join(content, "\n")
}

func getUserRole(parentSpan opentracing.Span) string {
    span := opentracing.GlobalTracer().StartSpan("getUserRole", opentracing.ChildOf(parentSpan.Context()))
    defer span.Finish()
    time.Sleep(time.Second * 1)
    fmt.Println("getUserRole 打印baggage")
    span.Context().ForeachBaggageItem(func(k, v string) bool {
       fmt.Println(k, v)
       return true
    })
    return "VIP"
}

image.png

image.png

http集成opentracing

  • server/main.go
package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/opentracing/opentracing-go"
    "github.com/opentracing/opentracing-go/ext"
    "github.com/uber/jaeger-client-go"
    "github.com/uber/jaeger-client-go/config"
    "github.com/uber/jaeger-lib/metrics"
    "io"
    "log"
    "net/http"
    "time"
)

func NewJaegerTracer(ServiceName string, jaegerHost string) (opentracing.Tracer, io.Closer, error) {
    configuration := config.Configuration{
       ServiceName: ServiceName,
       Sampler: &config.SamplerConfig{
          Type:  jaeger.SamplerTypeConst,
          Param: 1,
       },
       Reporter: &config.ReporterConfig{
          BufferFlushInterval: 1 * time.Second,
          LogSpans:            true,
          LocalAgentHostPort:  jaegerHost,
       },
    }
    tracer, closer, err := configuration.NewTracer(config.Logger(jaeger.NullLogger), config.Metrics(metrics.NullFactory))
    if err != nil {
       panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
    }
    opentracing.SetGlobalTracer(tracer)

    return tracer, closer, err
}

func InitHttpJaeger() {
    tracer, _, err := NewJaegerTracer("my_http_server", "127.0.0.1:6831")
    if err != nil {
       log.Fatal(err)
    }
    //defer closer.Close()
    opentracing.SetGlobalTracer(tracer)
}
func main() {
    InitHttpJaeger()
    router := gin.Default()
    router.Use(traceMiddleawre)
    router.GET("/greet", greet)
    router.Run("127.0.0.1:8080")
}

func traceMiddleawre(ctx *gin.Context) {
    clientSpanCtx, err := opentracing.GlobalTracer().Extract(
       opentracing.HTTPHeaders,
       opentracing.HTTPHeadersCarrier(ctx.Request.Header))
    if err != nil {
       log.Printf("extract trace context err: %v", err)
    }
    operatorName := ctx.Request.RequestURI
    serverSpan := opentracing.GlobalTracer().StartSpan(operatorName, ext.RPCServerOption(clientSpanCtx))
    defer serverSpan.Finish()
    for k, v := range ctx.Request.Header {
       if k == "Uber-Trace-Id" {
          continue
       }
       serverSpan.SetTag(k, v)
    }
    ctx.Next()
}

func greet(ctx *gin.Context) {
    time.Sleep(time.Second * 3)
    ctx.String(http.StatusOK, "Hello World")
}

  • client/main.go
package main

import (
    "context"
    "fmt"
    "github.com/opentracing/opentracing-go"
    "github.com/uber/jaeger-client-go"
    "github.com/uber/jaeger-client-go/config"
    "github.com/uber/jaeger-lib/metrics"
    "go.etcd.io/etcd/pkg/v3/stringutil"
    "io"
    "log"
    "net/http"
    "strconv"
    "time"
)

func main() {
    //InitHttpJaeger()  //如果这样初始化, 则会出现client span上报失败 `invalid parent span IDs=0f79e6fcbfb0a1fe; skipping clock skew adjustment`
    jaeger, closer, err := NewJaegerTracer("my_http_client", "127.0.0.1:6831")
    if err != nil {
       panic(err)
    }
    defer closer.Close()
    opentracing.SetGlobalTracer(jaeger)
    
    userId := 8
    ctx := context.WithValue(context.Background(), "trace_id", stringutil.RandString(20))
    ctx = context.WithValue(ctx, "user_id", strconv.Itoa(userId))
    fmt.Println(greet(ctx))

}

func NewJaegerTracer(ServiceName string, jaegerHost string) (opentracing.Tracer, io.Closer, error) {
    configuration := config.Configuration{
       ServiceName: ServiceName,
       Sampler: &config.SamplerConfig{
          Type:  jaeger.SamplerTypeConst,
          Param: 1,
       },
       Reporter: &config.ReporterConfig{
          BufferFlushInterval: 1 * time.Second,
          LogSpans:            true,
          LocalAgentHostPort:  jaegerHost,
       },
    }
    tracer, closer, err := configuration.NewTracer(config.Logger(jaeger.NullLogger), config.Metrics(metrics.NullFactory))
    if err != nil {
       panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
    }
    opentracing.SetGlobalTracer(tracer)
    return tracer, closer, err
}

func InitHttpJaeger() {
    jaeger, closer, err := NewJaegerTracer("my_http_server", "127.0.0.1:6831")
    if err != nil {
       log.Fatal(err)
    }
    defer closer.Close()
    opentracing.SetGlobalTracer(jaeger)
}

func greet(ctx context.Context) string {
    span := opentracing.GlobalTracer().StartSpan("greet")
    defer span.Finish()

    url := "http://127.0.0.1:8080/greet"
    client := http.Client{}
    request, err := http.NewRequest("GET", url, nil)
    request.Header.Set("trace_id", ctx.Value("trace_id").(string))
    request.Header.Set("user_id", ctx.Value("user_id").(string))
    //------------------------------------

    if span != nil {
       for k, v := range request.Header {
          span.SetTag(k, v[0])
       }
       if err := opentracing.GlobalTracer().Inject(
          span.Context(),
          opentracing.HTTPHeaders,
          opentracing.HTTPHeadersCarrier(request.Header),
       ); err != nil {
          log.Fatal(err)
       } else {
          for k, v := range request.Header { //header里多出了Uber-Trace-Id
             fmt.Println(k, v[0])
          }
       }
    }

    response, err := client.Do(request)
    if err != nil {
       log.Fatal(err)
    }
    defer response.Body.Close()
    bs, err := io.ReadAll(response.Body)
    if err != nil {
       log.Fatal(err)
    }
    return string(bs)
}
  • 测试
image.png

image.png

  • 出现的错误: 客户端span无法上报: invalid parent span IDs=0f79e6fcbfb0a1fe; skipping clock skew adjustment

image.png

解决: 不使用InitHttpJaeger(),将它的逻辑直接写到main

func main() {
    //InitHttpJaeger()  //如果这样初始化, 则会出现client span上报失败 `invalid parent span IDs=0f79e6fcbfb0a1fe; skipping clock skew adjustment`
    jaeger, closer, err := NewJaegerTracer("my_http_client", "127.0.0.1:6831")
    if err != nil {
       panic(err)
    }
    defer closer.Close()
    opentracing.SetGlobalTracer(jaeger)

grpc集成opentracing

  • server/main.go
package main

import (
    "context"
    "fmt"
    "github.com/opentracing/opentracing-go"
    "github.com/opentracing/opentracing-go/ext"
    "github.com/uber/jaeger-client-go"
    "github.com/uber/jaeger-client-go/config"
    "github.com/uber/jaeger-lib/metrics"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    pb "grpc-demo/grpc_trace/proto"
    "io"
    "log"
    "net"
    "time"
)

type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func NewJaegerTracer(ServiceName string, jaegerHost string) (opentracing.Tracer, io.Closer, error) {
    configuration := config.Configuration{
       ServiceName: ServiceName,
       Sampler: &config.SamplerConfig{
          Type:  jaeger.SamplerTypeConst,
          Param: 1,
       },
       Reporter: &config.ReporterConfig{
          BufferFlushInterval: 1 * time.Second,
          LogSpans:            true,
          LocalAgentHostPort:  jaegerHost,
       },
    }
    tracer, closer, err := configuration.NewTracer(config.Logger(jaeger.NullLogger), config.Metrics(metrics.NullFactory))
    if err != nil {
       panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
    }
    opentracing.SetGlobalTracer(tracer)
    return tracer, closer, err
}

func main() {
    jaeger, closer, err := NewJaegerTracer("my_grpc_server", "127.0.0.1:6831")
    if err != nil {
       log.Fatal(err)
    }
    defer closer.Close()
    opentracing.SetGlobalTracer(jaeger)

    lis, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", 50051))
    if err != nil {
       log.Fatalf("failed to listen: %v", err)
    }

    s := grpc.NewServer(grpc.UnaryInterceptor(traceInterceptor))
    pb.RegisterGreeterServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
       log.Fatalf("failed to serve: %v", err)
    }
}

func traceInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
       md = metadata.New(map[string]string{})
    }
    for k, v := range md {
       fmt.Println(k, v[0])
    }
    clientSpanCtx, err := opentracing.GlobalTracer().Extract(opentracing.TextMap, MDCarrier(md))
    if err != nil {
       log.Printf("extract trace from metadata: %v", err)
       return handler(ctx, req)
    }
    span := opentracing.GlobalTracer().StartSpan(
       info.FullMethod,                    //完整的方法名为span名
       ext.RPCServerOption(clientSpanCtx), //把client的metadata带进来, 一方面指明集成关系, 另一方便指明span.kine=server
       opentracing.Tag{Key: string(ext.Component), Value: "gRPC服务端"},
    )
    for k, v := range md {
       if k == "uber-trace-id" {
          continue
       }
       span.SetTag(k, v[0]) //为span加tag
    }
    defer span.Finish()
    return handler(ctx, req)
}

// MDCarrier 类似TextMapCarrier, 但是能承载的mapkey是slice
// type TextMapCarrier map[string]string
// 实现opentracing.TextMapReader和opentracing.TextMapWriter接口
type MDCarrier map[string][]string

func (m MDCarrier) ForeachKey(handler func(key string, val string) error) error {
    for k, strs := range m {
       for _, v := range strs {
          if err := handler(k, v); err != nil {
             return err
          }

       }
    }
    return nil
}

func (m MDCarrier) Set(key, val string) {
    m[key] = append(m[key], val)
}
  • client/main.go
package main

import (
    "context"
    "fmt"
    "github.com/opentracing/opentracing-go"
    "github.com/opentracing/opentracing-go/ext"
    "github.com/uber/jaeger-client-go"
    "github.com/uber/jaeger-client-go/config"
    "github.com/uber/jaeger-lib/metrics"
    "go.etcd.io/etcd/pkg/v3/stringutil"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "google.golang.org/grpc/metadata"
    pb "grpc-demo/grpc_trace/proto"
    "io"
    "log"
    "strconv"
    "time"
)

func NewJaegerTracer(ServiceName string, jaegerHost string) (opentracing.Tracer, io.Closer, error) {
    configuration := config.Configuration{
       ServiceName: ServiceName,
       Sampler: &config.SamplerConfig{
          Type:  jaeger.SamplerTypeConst,
          Param: 1,
       },
       Reporter: &config.ReporterConfig{
          BufferFlushInterval: 1 * time.Second,
          LogSpans:            true,
          LocalAgentHostPort:  jaegerHost,
       },
    }
    tracer, closer, err := configuration.NewTracer(config.Logger(jaeger.NullLogger), config.Metrics(metrics.NullFactory))
    if err != nil {
       panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err))
    }
    opentracing.SetGlobalTracer(tracer)
    return tracer, closer, err
}

func main() {
    jaeger, closer, err := NewJaegerTracer("my_grpc_client", "127.0.0.1:6831")
    if err != nil {
       log.Fatal(err)
    }
    defer closer.Close()
    opentracing.SetGlobalTracer(jaeger)

    conn, err := grpc.Dial("127.0.0.1:50051",
       grpc.WithTransportCredentials(insecure.NewCredentials()),
       grpc.WithChainUnaryInterceptor(traceInterCeptor),
    )

    if err != nil {
       log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)
    userId := 8
    ctx := context.WithValue(context.Background(), "user_id", strconv.Itoa(userId))
    ctx = metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{"org": "xxx"}))
    SayHello(ctx, c)
}

func SayHello(ctx context.Context, client pb.GreeterClient) string {
    r, err := client.SayHello(ctx, &pb.HelloRequest{Name: fmt.Sprintf("%s", "mmm")})
    if err != nil {
       log.Fatalf("could not greet: %v", err)
    }
    return r.Message
}

func traceInterCeptor(ctx context.Context, method string, req any, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    traceId := stringutil.RandString(20)
    userId := ctx.Value("user_id").(string)

    //ctx = metadata.AppendToOutgoingContext(ctx, "trace_id", traceId)
    //ctx = metadata.AppendToOutgoingContext(ctx, "user_id", userId)
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
       md = metadata.New(map[string]string{})
    }
    md.Append("trace_id", traceId)
    md.Append("user_id", userId)
    span := opentracing.GlobalTracer().StartSpan(
       method, //grpc方法名为Span命名
       opentracing.Tag{Key: string(ext.Component), Value: "gRPC客户端"}, //ext.Component是一个全局变量  string(ext.Component)="component"
       ext.SpanKindRPCClient,
    )
    defer span.Finish()
    for k, v := range md {
       span.SetTag(k, v[0])
       fmt.Println(k, v[0])
    }
    if err := opentracing.GlobalTracer().Inject(
       span.Context(),
       opentracing.TextMap,
       MDCarrier(md)); err != nil {
       log.Printf("SpanContext inject error: %v", err)
    }
    fmt.Println("---------------------------")
    for k, v := range md {
       fmt.Println(k, v[0]) //md里多出一个uber-trace-id
    }
    fmt.Println("---------------------------")
    ctx = metadata.NewOutgoingContext(ctx, md) //执行rpc时,把user-trace-id传给对方,否则对方在执行Extrace时候报错, SpanContext not found inExtrace carrier
    err := invoker(ctx, method, req, reply, cc, opts...)
    return err
}

// MDCarrier 类似TextMapCarrier, 但是能承载的mapkey是slice
// type TextMapCarrier map[string]string
// 实现opentracing.TextMapReader和opentracing.TextMapWriter接口
type MDCarrier map[string][]string

func (m MDCarrier) ForeachKey(handler func(key string, val string) error) error {
    for k, strs := range m {
       for _, v := range strs {
          if err := handler(k, v); err != nil {
             return err
          }

       }
    }
    return nil
}

func (m MDCarrier) Set(key, val string) {
    m[key] = append(m[key], val)
}

image.png