• 【GO】LGTM_Grafana_gozero_配置trace(4)_代码实操及追踪


    最近在尝试用 LGTM 来实现 Go 微服务的可观测性,就顺便整理一下文档。

    Tempo 会分为 4 篇文章:

    1. Tempo 的架构
    2. 官网测试实操跑通
    3. gin 框架发送 trace 数据到 tempo
    4. go-zero 微服务框架发送数据到 tempo

    本文就是写一下如何在 go-zero 微服务框架里面配置 trace 及 trace 的 handler 在哪里

    代码地址:lgtm/example/go-zero-lgtm at main · zxmfke/lgtm (github.com)

    这个代码里面包含一个 order 的 api 服务和一个 user 的 rpc 服务


    go-zero: 1.5.5
    go: 1.20.3


    go-zero 的服务接入 trace,是比较简单的,只需要在配置项中,配置 Telemetry 里面的参数就可以了。

    Telemetry 配置

    在 /etc 下有配置文件,其中配置:

    ...
    
    Telemetry:
      Name: "order"
      Endpoint: "127.0.0.1:4318"
      Batcher: "otlphttp"
    
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    源码(core/trace/config.go)中可以配置的参数:

    type Config struct {
    	Name     string  `json:",optional"`
    	Endpoint string  `json:",optional"`
    	Sampler  float64 `json:",default=1.0"`
    	Batcher  string  `json:",default=jaeger,options=jaeger|zipkin|otlpgrpc|otlphttp|file"`
    	// OtlpHeaders represents the headers for OTLP gRPC or HTTP transport.
    	// For example:
    	//  uptrace-dsn: 'http://project2_secret_token@localhost:14317/2'
    	OtlpHeaders map[string]string `json:",optional"`
    	// OtlpHttpPath represents the path for OTLP HTTP transport.
    	// For example
    	// /v1/traces
    	OtlpHttpPath string `json:",optional"`
    	// Disabled indicates whether StartAgent starts the agent.
    	Disabled bool `json:",optional"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    主要就是配置:

    • Name:服务名称
    • Endpoint:Trace provider 的 IP 地址
    • Batcher:用哪一种 provider 类型

    注意,Disabled 如果没有设置为 true,就默认表示发送 trace 数据

    Grafana Tempo Trace Data

    请求接口后,trace 数据会发送到 Grafana 的 Tempo 里面,如下图:

    在这里插入图片描述

    可以看到分为三段:

    • order-api 收到请求的时候
    • order-api 请求 user-rpc 的时候
    • user-rpc 收到请求的时候

    接下来我们可以追踪一下源码看看是在哪里发送的 trace 数据

    追踪代码
    初始化

    order

    order 是 api 服务,Telemetry 初始化的逻辑:

    • order.go

      // L24
      server := rest.MustNewServer(c.RestConf)
      
      • 1
      • 2
    • go-zero/rest/server.go

      // L35
      func MustNewServer(c RestConf, opts ...RunOption) *Server {
      	server, err := NewServer(c, opts...)
      	...
      }
      
      // L46
      func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
      	if err := c.SetUp(); err != nil {
      		return nil, err
      	}
      	...
      
      	return server, nil
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    • go-zero/core/sevice/serviceconf.go

      // L44
      func (sc ServiceConf) SetUp() error {
      	...
      	trace.StartAgent(sc.Telemetry)
      	...
      	return nil
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • go-zero/core/trace/agent.go

      // L39
      func StartAgent(c Config) {
      	...
      
      	// if error happens, let later calls run.
      	if err := startAgent(c); err != nil {
      		return
      	}
      
      	...
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

    user

    • user.go

      // L29
      s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
      	userclient.RegisterUserServer(grpcServer, srv)
      
          if c.Mode == service.DevMode || c.Mode == service.TestMode {
      		reflection.Register(grpcServer)
      	}
      })
      defer s.Stop()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • go-zero/zrpc/server.go

      // L23 
      func MustNewServer(c RpcServerConf, register internal.RegisterFn) *RpcServer {
      	server, err := NewServer(c, register)
      	logx.Must(err)
      	return server
      }
      
      // L30
      func NewServer(c RpcServerConf, register internal.RegisterFn) (*RpcServer, error) {
      	...
          
      	if err = c.SetUp(); err != nil {
      		return nil, err
      	}
      
      	return rpcServer, nil
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • Setup() 跟 order 的一样了

    Order api 的 trace handler

    之前看过源码,go-zero 的 api 和 rpc 是分开两个目录,所以 api 的代码一定在 go-zero/rest 下面。 要在请求的收到的时候发送一个 trace 数据,只有可能以中间件的形式加入,因此可以找到 rest 下的 handler 文件夹,里面就有一个 tracinghandler.go。

    源码位置:go-zero/rest/handler/tracinghandler.go

    这个就类似上一篇的 gin server 加 trace middleware 一样。

    func TracingHandler(serviceName, path string) func(http.Handler) http.Handler {
    	return func(next http.Handler) http.Handler {
    		propagator := otel.GetTextMapPropagator()
    		tracer := otel.GetTracerProvider().Tracer(trace.TraceName)
    
    		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    			ctx := propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
    			spanName := path
    			if len(spanName) == 0 {
    				spanName = r.URL.Path
    			}
    			spanCtx, span := tracer.Start(
    				ctx,
    				spanName,
    				oteltrace.WithSpanKind(oteltrace.SpanKindServer),
    				oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(
    					serviceName, spanName, r)...),
    			)
    			defer span.End()
    
    			// convenient for tracking error messages
    			propagator.Inject(spanCtx, propagation.HeaderCarrier(w.Header()))
    			next.ServeHTTP(w, r.WithContext(spanCtx))
    		})
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    User rpc 的 trace handler

    同理,user 是 rpc 服务,所以就到 zrpc 下面找,按照惯性,找到 go-zero/zrpc/internal/serverinterceptors ,这个时候就可以看到 tracinginterceptor.go

    源码位置:go-zero/zrpc/internal/serverinterceptors/tracinginterceptor.go

    func UnaryTracingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
        handler grpc.UnaryHandler) (interface{}, error) {
        ctx, span := startSpan(ctx, info.FullMethod)
        defer span.End()
    
        ztrace.MessageReceived.Event(ctx, 1, req)
        resp, err := handler(ctx, req)
        if err != nil {
           s, ok := status.FromError(err)
           if ok {
              span.SetStatus(codes.Error, s.Message())
              span.SetAttributes(ztrace.StatusCodeAttr(s.Code()))
              ztrace.MessageSent.Event(ctx, 1, s.Proto())
           } else {
              span.SetStatus(codes.Error, err.Error())
           }
           return nil, err
        }
    
        span.SetAttributes(ztrace.StatusCodeAttr(gcodes.OK))
        ztrace.MessageSent.Event(ctx, 1, resp)
    
        return resp, nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    找到这个,但是没有发现 order-api 调用 user 的 handler。就很奇怪,又返回 rest 里面找,不过还是没找到,后面才发现 zrpc/internal 下面有一个 clientinterceptors ,原来在这边。

    func UnaryTracingInterceptor(ctx context.Context, method string, req, reply interface{},
    	cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    	ctx, span := startSpan(ctx, method, cc.Target())
    	defer span.End()
    
    	ztrace.MessageSent.Event(ctx, 1, req)
    	ztrace.MessageReceived.Event(ctx, 1, reply)
    
    	if err := invoker(ctx, method, req, reply, cc, opts...); err != nil {
    		s, ok := status.FromError(err)
    		if ok {
    			span.SetStatus(codes.Error, s.Message())
    			span.SetAttributes(ztrace.StatusCodeAttr(s.Code()))
    		} else {
    			span.SetStatus(codes.Error, err.Error())
    		}
    		return err
    	}
    
    	span.SetAttributes(ztrace.StatusCodeAttr(gcodes.OK))
    	return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    ror(err)
    if ok {
    span.SetStatus(codes.Error, s.Message())
    span.SetAttributes(ztrace.StatusCodeAttr(s.Code()))
    } else {
    span.SetStatus(codes.Error, err.Error())
    }
    return err
    }

    span.SetAttributes(ztrace.StatusCodeAttr(gcodes.OK))
    return nil
    
    • 1
    • 2

    }

    
    
    • 1
  • 相关阅读:
    YOLOv5 最详细的源码逐行解读(二: 网络结构)
    2022最新Java面试宝典(史上最全,BAT大厂面试必备,用心看完该篇就够了,建议先关注点赞加收藏)
    springCloud系列--Feigin--开启 Gzip 压缩
    MAML++:HOW TO TRAIN YOUR MAML论文精读
    生信工作流框架搭建 | 02-nextflow 实战
    Mysql整理-主从复制
    5毛VS600亿,食品安全问题是卫龙上市最大的拦路虎?
    Springboot集成Quartz
    Linux环境配置升级python
    【考研数学】二. 一元函数积分学
  • 原文地址:https://blog.csdn.net/weixin_40242845/article/details/133256911