写点什么

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

作者:非晓为骁
  • 2023-09-25
    福建
  • 本文字数:3250 字

    阅读完需:约 11 分钟

最近在尝试用 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 的服务接入 trace,是比较简单的,只需要在配置项中,配置 Telemetry 里面的参数就可以了。

Telemetry 配置

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


...
Telemetry: Name: "order" Endpoint: "127.0.0.1:4318" Batcher: "otlphttp"
...
复制代码


源码(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"`}
复制代码


主要就是配置:


  • 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

// L24server := rest.MustNewServer(c.RestConf)
复制代码
  • go-zero/rest/server.go

// L35func MustNewServer(c RestConf, opts ...RunOption) *Server {	server, err := NewServer(c, opts...)	...}
// L46func NewServer(c RestConf, opts ...RunOption) (*Server, error) { if err := c.SetUp(); err != nil { return nil, err } ...
return server, nil}
复制代码
  • go-zero/core/sevice/serviceconf.go

// L44func (sc ServiceConf) SetUp() error {	...	trace.StartAgent(sc.Telemetry)	...	return nil}
复制代码
  • go-zero/core/trace/agent.go

// L39func StartAgent(c Config) {	...
// if error happens, let later calls run. if err := startAgent(c); err != nil { return }
...}
复制代码

user


  • user.go

// L29s := 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()
复制代码
  • go-zero/zrpc/server.go

// L23 func MustNewServer(c RpcServerConf, register internal.RegisterFn) *RpcServer {	server, err := NewServer(c, register)	logx.Must(err)	return server}
// L30func NewServer(c RpcServerConf, register internal.RegisterFn) (*RpcServer, error) { ... if err = c.SetUp(); err != nil { return nil, err }
return rpcServer, nil}
复制代码
  • 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)) }) }}
复制代码
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}
复制代码


找到这个,但是没有发现 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}
复制代码


用户头像

非晓为骁

关注

no pain no gain 2019-04-10 加入

用我的勤奋,一点一点地努力,提升自己的能力,拓展自己的视野,提高自己的认知。 我的知乎:https://www.zhihu.com/people/zhengfke

评论

发布
暂无评论
【GO】LGTM_Grafana_gozero_配置trace(4)_代码实操及追踪_golang_非晓为骁_InfoQ写作社区