将跟踪添加到 Cloud Run 中的内部方法

问题描述

我们希望为 Cloud Run 上部署的服务中使用的方法添加跟踪。

跟踪已提供的 Cloud Run 请求:


假设我们有以下 gRPC 方法

func (s *myServiceService) SyncTable(ctx context.Context,req *pb.SyncTableRequest) (*longrunning.Operation,error) {

    //.... some stuff here...
    
    // making a call to the internal method,which has a tracing span
    err := dropRequestOnStorage(ctx,...)
    if err != nil {
        return nil,err
    }
    
    return op,nil
}

以下是一个内部方法示例,我们向该方法添加了 Trace span 并由主 gRPC 方法调用

// dropRequestOnStorage loads the requests on the relevant bucket.
func dropRequestOnStorage(ctx context.Context,filename string,operationID string,req *pb.ExtractDataRequest) error {
 
    // add tracing to this method.
    ctx,span := otel.Tracer("").Start(ctx,"dropRequestOnStorage")
    defer span.End()

    // load json object to storage
    reqByte,err := protojson.Marshal(req)
    if err != nil {
        fmt.Println(err)
    }
    wc := storageClient.Bucket("my-bucket-with-cool-stuff").Object(filename).NewWriter(ctx)
    wc.ContentType = "application/json"
    
    _,err = wc.Write(reqByte)
    if err != nil {
        fmt.Println(err)
    }
    wc.Close()
    fmt.Println(filename)
    return nil
}

查看 Google Cloud Run 的跟踪,我看到了上述方法的跟踪:

Google Cloud Run tracing

尽管将 context 从主 gRPC 传递到内部方法,但不会将 Tracing 拉到底层内部。内部方法生成的跟踪不会“接收”作为父级的主 gRPC 跟踪。

这是因为 Cloud Run 提供的认跟踪是由 Cloud Run 内部完成的吗?因此不适用于 gRPC 方法的上下文?

解决方法

使用 gRPC 拦截器进行跟踪


实现此功能的唯一方法是添加 gRPC 拦截器来为每个 gRPC 方法创建跟踪范围。

package main

import (
    "context"
    texporter "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
    "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/propagation"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    "google.golang.org/grpc"
    "log"
    "net"
    "os"
)

func init() {

    // Pre-declare err to avoid shadowing.
    var err error

    // initialising tracing exporter
    //exporter,err := stdout.NewExporter(stdout.WithPrettyPrint())
    exporter,err := texporter.NewExporter(texporter.WithProjectID("alis-de"))
    if err != nil {
        log.Fatalf("texporter.NewExporter: %v",err)
    }

    tp := sdktrace.NewTracerProvider(
        sdktrace.WithSampler(sdktrace.AlwaysSample()),sdktrace.WithSyncer(exporter),)

    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{},propagation.Baggage{}))
}

func main() {
    log.Printf("starting server...")
    
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
        log.Printf("Defaulting to port %s",port)
    }

    listener,err := net.Listen("tcp",":"+port)
    if err != nil {
        log.Fatalf("net.Listen: %v",err)
    }
    
    // Attaching grpc interceptors to automatically enable tracing at gRCP methods
    grpcServer := grpc.NewServer(
        grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),)

    pb.RegisterOperationsServer(grpcServer,&operationsService{})

    if err = grpcServer.Serve(listener); err != nil {
        log.Fatal(err)
    }
}

跟踪现在会进入控制台:

Tracing using gRPC Interceptors

但是,查看跟踪记录,现在(不幸的是??)有两个跟踪条目:

  1. Cloud Run 提供的默认跟踪(没有子跟踪)
  2. gRPC 拦截器生成的新跟踪(带有反映内部调用方法的子跟踪)

Duplicate trace entries