1

I am just starting with OpenTelemetry and have created two (micro)services for this purpose: Standard and GeoMap.

The end-user sends requests to the Standard service, who in turn sends requests to GeoMap to fetch informations before returning the result to the end-user. I am using gRPC for all communications.

I have instrumented my functions as such:

For Standard:

func (s *server) GetStandard(ctx context.Context, in *pb.GetStandardRequest) (*pb.GetStandardResponse, error) {

    newCtx, span := otel.Tracer(name).Start(ctx, "GetStandard")
    span.SpanContext()
    defer span.End()

    countryInfo, err := geoMapServiceClient.GetCountry(newCtx,
        &pb.GetCountryRequest{
            Name: in.Name,
        })

    ....
    
    return &pb.GetStandardResponse{
        Standard: standard,
    }, nil

}

For GeoMap

func (s *geomapService) GetCountry(ctx context.Context, in *pb.GetCountryRequest) (*pb.GetCountryResponse, error) {

    _, span := otel.Tracer(name).Start(ctx, "GetCountry")
    defer span.End()

    span.SetAttributes(attribute.String("country", in.Name))

    ...

    return &pb.GetCountryResponse{
        Country: &country,
    }, nil

}

Both services are configured to send their traces to a Jaeger Backend:

func tracerProvider(url string) (*tracesdk.TracerProvider, error) {
    // Create the Jaeger exporter
    exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
    if err != nil {
        return nil, err
    }
    tp := tracesdk.NewTracerProvider(
        tracesdk.WithBatcher(exp),
        tracesdk.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceName(service),
            attribute.String("environment", environment),
            attribute.Int64("ID", id),
        )),
    )
    return tp, nil
}

func main() {

    tp, err := tracerProvider("http://localhost:14268/api/traces")
    if err != nil {
        log.Fatal(err)
    }

    defer func() {
        if err := tp.Shutdown(context.Background()); err != nil {
            log.Fatal(err)
        }
    }()

    otel.SetTracerProvider(tp)
    
    ...
}

I would expect the span generated in the GeoMap service to be a child span of the Standard service (since it is the Standard service who is making the request through the geoMapServiceClient). However these spans are totally unrelated in Jaeger:

GeoMap span

Standard span

What am I missing here?

EDIT:

I have managed to include interceptors as @Peter suggested in the comments. Now spans are linked to each other and I can see that my parent span from the StandardService is making a request to the GeoMapService:

spans linked

However, I have defined an attribute in my function call (see the span.SetAttributes(attribute.String("country", in.Name))) but I am unable to see that value from within my child span.

For that I have to switch to the other trace generated by the service GeoMap:

GeoMap trace

I don't need to have two different traces actually, I would like to have everything in the same trace (one end-user query should result in a single trace). How should I change my code from here?

Mit94
  • 718
  • 8
  • 28
  • 1
    Have you added the [interceptors](https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc) to server and client? – Peter Jul 02 '23 at 09:41
  • @Peter No I did not at first. Now that I added I can see the spans being linked to each other. However I don't see the attributes that I have set from in my GeoMap service and have to look for the trace generated by that other service for that. I have edited my answer. Thank you for you help – Mit94 Jul 02 '23 at 11:33
  • @Mit94 you should post your second question as a separate question. – Dmitry Harnitski Jul 03 '23 at 03:58
  • @DmitryHarnitski I did so: https://stackoverflow.com/questions/76603368/unify-spans-of-different-microservices-with-opentelemetry – Mit94 Jul 03 '23 at 09:11

0 Answers0