google.golang.org/grpc@v1.74.2/stats/opentelemetry/client_tracing.go (about)

     1  /*
     2   * Copyright 2024 gRPC authors.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package opentelemetry
    18  
    19  import (
    20  	"context"
    21  	"log"
    22  	"strings"
    23  
    24  	otelcodes "go.opentelemetry.io/otel/codes"
    25  	"go.opentelemetry.io/otel/trace"
    26  	"google.golang.org/grpc"
    27  	grpccodes "google.golang.org/grpc/codes"
    28  	"google.golang.org/grpc/stats"
    29  	otelinternaltracing "google.golang.org/grpc/stats/opentelemetry/internal/tracing"
    30  	"google.golang.org/grpc/status"
    31  )
    32  
    33  const (
    34  	delayedResolutionEventName = "Delayed name resolution complete"
    35  	tracerName                 = "grpc-go"
    36  )
    37  
    38  type clientTracingHandler struct {
    39  	options Options
    40  }
    41  
    42  func (h *clientTracingHandler) initializeTraces() {
    43  	if h.options.TraceOptions.TracerProvider == nil {
    44  		log.Printf("TracerProvider is not provided in client TraceOptions")
    45  		return
    46  	}
    47  }
    48  
    49  func (h *clientTracingHandler) unaryInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    50  	ctx, _ = getOrCreateCallInfo(ctx, cc, method, opts...)
    51  
    52  	var span trace.Span
    53  	ctx, span = h.createCallTraceSpan(ctx, method)
    54  	err := invoker(ctx, method, req, reply, cc, opts...)
    55  	h.finishTrace(err, span)
    56  	return err
    57  }
    58  
    59  func (h *clientTracingHandler) streamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
    60  	ctx, _ = getOrCreateCallInfo(ctx, cc, method, opts...)
    61  
    62  	var span trace.Span
    63  	ctx, span = h.createCallTraceSpan(ctx, method)
    64  	callback := func(err error) { h.finishTrace(err, span) }
    65  	opts = append([]grpc.CallOption{grpc.OnFinish(callback)}, opts...)
    66  	return streamer(ctx, desc, cc, method, opts...)
    67  }
    68  
    69  // finishTrace sets the span status based on the RPC result and ends the span.
    70  // It is used to finalize tracing for both unary and streaming calls.
    71  func (h *clientTracingHandler) finishTrace(err error, ts trace.Span) {
    72  	s := status.Convert(err)
    73  	if s.Code() == grpccodes.OK {
    74  		ts.SetStatus(otelcodes.Ok, s.Message())
    75  	} else {
    76  		ts.SetStatus(otelcodes.Error, s.Message())
    77  	}
    78  	ts.End()
    79  }
    80  
    81  // traceTagRPC populates provided context with a new span using the
    82  // TextMapPropagator supplied in trace options and internal itracing.carrier.
    83  // It creates a new outgoing carrier which serializes information about this
    84  // span into gRPC Metadata, if TextMapPropagator is provided in the trace
    85  // options. if TextMapPropagator is not provided, it returns the context as is.
    86  func (h *clientTracingHandler) traceTagRPC(ctx context.Context, ai *attemptInfo, nameResolutionDelayed bool) (context.Context, *attemptInfo) {
    87  	// Add a "Delayed name resolution complete" event to the call span
    88  	// if there was name resolution delay. In case of multiple retry attempts,
    89  	// ensure that event is added only once.
    90  	callSpan := trace.SpanFromContext(ctx)
    91  	ci := getCallInfo(ctx)
    92  	if nameResolutionDelayed && !ci.nameResolutionEventAdded.Swap(true) && callSpan.SpanContext().IsValid() {
    93  		callSpan.AddEvent(delayedResolutionEventName)
    94  	}
    95  	mn := "Attempt." + strings.Replace(ai.method, "/", ".", -1)
    96  	tracer := h.options.TraceOptions.TracerProvider.Tracer(tracerName, trace.WithInstrumentationVersion(grpc.Version))
    97  	ctx, span := tracer.Start(ctx, mn)
    98  	carrier := otelinternaltracing.NewOutgoingCarrier(ctx)
    99  	h.options.TraceOptions.TextMapPropagator.Inject(ctx, carrier)
   100  	ai.traceSpan = span
   101  	return carrier.Context(), ai
   102  }
   103  
   104  // createCallTraceSpan creates a call span to put in the provided context using
   105  // provided TraceProvider. If TraceProvider is nil, it returns context as is.
   106  func (h *clientTracingHandler) createCallTraceSpan(ctx context.Context, method string) (context.Context, trace.Span) {
   107  	mn := "Sent." + strings.Replace(removeLeadingSlash(method), "/", ".", -1)
   108  	tracer := h.options.TraceOptions.TracerProvider.Tracer(tracerName, trace.WithInstrumentationVersion(grpc.Version))
   109  	ctx, span := tracer.Start(ctx, mn, trace.WithSpanKind(trace.SpanKindClient))
   110  	return ctx, span
   111  }
   112  
   113  // TagConn exists to satisfy stats.Handler for tracing.
   114  func (h *clientTracingHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
   115  	return ctx
   116  }
   117  
   118  // HandleConn exists to satisfy stats.Handler for tracing.
   119  func (h *clientTracingHandler) HandleConn(context.Context, stats.ConnStats) {}
   120  
   121  // TagRPC implements per RPC attempt context management for traces.
   122  func (h *clientTracingHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
   123  	ctx, ai := getOrCreateRPCAttemptInfo(ctx)
   124  	ctx, ai = h.traceTagRPC(ctx, ai, info.NameResolutionDelay)
   125  	return setRPCInfo(ctx, &rpcInfo{ai: ai})
   126  }
   127  
   128  // HandleRPC handles per RPC tracing implementation.
   129  func (h *clientTracingHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
   130  	ri := getRPCInfo(ctx)
   131  	if ri == nil {
   132  		logger.Error("ctx passed into client side tracing handler trace event handling has no client attempt data present")
   133  		return
   134  	}
   135  	populateSpan(rs, ri.ai)
   136  }