From 7bb2cf86bd0c5bdf5a0a9e2d8a36fc16af3bd2ae Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 31 Jul 2025 12:08:23 -0700 Subject: [PATCH 1/7] Remove the deprecated otelgrpc.StreamClientInterceptor Fix #7105 --- .../grpc/otelgrpc/benchmark_test.go | 9 - .../google.golang.org/grpc/otelgrpc/go.mod | 1 - .../grpc/otelgrpc/grpc_test.go | 28 - .../grpc/otelgrpc/interceptor.go | 57 -- .../grpc/otelgrpc/interceptor_test.go | 570 ------------------ .../grpc/otelgrpc/stats_handlertest_test.go | 124 +++- 6 files changed, 122 insertions(+), 667 deletions(-) delete mode 100644 instrumentation/google.golang.org/grpc/otelgrpc/interceptor_test.go diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/benchmark_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/benchmark_test.go index 6819a430e0c..92a5cd4a999 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/benchmark_test.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/benchmark_test.go @@ -14,7 +14,6 @@ import ( pb "google.golang.org/grpc/interop/grpc_testing" "google.golang.org/grpc/test/bufconn" - "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/test" ) @@ -68,11 +67,3 @@ func benchmark(b *testing.B, cOpt []grpc.DialOption, sOpt []grpc.ServerOption) { func BenchmarkNoInstrumentation(b *testing.B) { benchmark(b, nil, nil) } - -func BenchmarkStreamClientInterceptor(b *testing.B) { - benchmark(b, []grpc.DialOption{ - grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor( - otelgrpc.WithTracerProvider(tracerProvider), - )), - }, nil) -} diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/go.mod b/instrumentation/google.golang.org/grpc/otelgrpc/go.mod index a3ce8322500..9d698c343ca 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/go.mod +++ b/instrumentation/google.golang.org/grpc/otelgrpc/go.mod @@ -9,7 +9,6 @@ require ( go.opentelemetry.io/otel/sdk v1.37.0 go.opentelemetry.io/otel/sdk/metric v1.37.0 go.opentelemetry.io/otel/trace v1.37.0 - go.uber.org/goleak v1.3.0 google.golang.org/grpc v1.74.2 google.golang.org/protobuf v1.36.6 ) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/grpc_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/grpc_test.go index f2352bc3860..b453cbfec65 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/grpc_test.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/grpc_test.go @@ -14,7 +14,6 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.34.0" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -73,33 +72,6 @@ func doCalls(ctx context.Context, client pb.TestServiceClient) { test.DoPingPong(ctx, client) } -func TestInterceptors(t *testing.T) { - t.Setenv("OTEL_METRICS_EXEMPLAR_FILTER", "always_off") - - clientStreamSR := tracetest.NewSpanRecorder() - clientStreamTP := trace.NewTracerProvider(trace.WithSpanProcessor(clientStreamSR)) - - listener, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err, "failed to open port") - client := newGrpcTest(t, listener, - []grpc.DialOption{ - //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. - grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor( - otelgrpc.WithTracerProvider(clientStreamTP), - otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), - )), - }, - []grpc.ServerOption{}, - ) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - doCalls(ctx, client) - - t.Run("StreamClientSpans", func(t *testing.T) { - checkStreamClientSpans(t, clientStreamSR.Ended(), listener.Addr().String()) - }) -} - func checkStreamClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) { require.Len(t, spans, 3) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go index c544b47bed9..de060beedbd 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go @@ -138,63 +138,6 @@ func (w *clientStream) endSpan(err error) { w.span.End() } -// StreamClientInterceptor returns a grpc.StreamClientInterceptor suitable -// for use in a grpc.NewClient call. -// -// Deprecated: Use [NewClientHandler] instead. -func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor { - cfg := newConfig(opts) - tracer := cfg.TracerProvider.Tracer( - ScopeName, - trace.WithInstrumentationVersion(Version()), - ) - - return func( - ctx context.Context, - desc *grpc.StreamDesc, - cc *grpc.ClientConn, - method string, - streamer grpc.Streamer, - callOpts ...grpc.CallOption, - ) (grpc.ClientStream, error) { - i := &InterceptorInfo{ - Method: method, - Type: StreamClient, - } - if cfg.InterceptorFilter != nil && !cfg.InterceptorFilter(i) { - return streamer(ctx, desc, cc, method, callOpts...) - } - - name, attr := telemetryAttributes(method, cc.Target()) - - startOpts := append([]trace.SpanStartOption{ - trace.WithSpanKind(trace.SpanKindClient), - trace.WithAttributes(attr...), - }, - cfg.SpanStartOptions..., - ) - - ctx, span := tracer.Start( - ctx, - name, - startOpts..., - ) - - ctx = inject(ctx, cfg.Propagators) - - s, err := streamer(ctx, desc, cc, method, callOpts...) - if err != nil { - grpcStatus, _ := status.FromError(err) - span.SetStatus(codes.Error, grpcStatus.Message()) - span.SetAttributes(statusCodeAttr(grpcStatus.Code())) - span.End() - return s, err - } - stream := wrapClientStream(s, desc, span, cfg) - return stream, nil - } -} - // telemetryAttributes returns a span name and span and metric attributes from // the gRPC method and peer address. func telemetryAttributes(fullMethod, serverAddr string) (string, []attribute.KeyValue) { diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor_test.go deleted file mode 100644 index 5b1c80066d0..00000000000 --- a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor_test.go +++ /dev/null @@ -1,570 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package otelgrpc_test - -import ( - "context" - "errors" - "io" - "net" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/sdk/trace/tracetest" - semconv "go.opentelemetry.io/otel/semconv/v1.34.0" - oteltrace "go.opentelemetry.io/otel/trace" - "go.uber.org/goleak" - "google.golang.org/grpc" - grpc_codes "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/interop/grpc_testing" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/test/bufconn" - - "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" -) - -func getSpanFromRecorder(sr *tracetest.SpanRecorder, name string) (trace.ReadOnlySpan, bool) { - for _, s := range sr.Ended() { - if s.Name() == name { - return s, true - } - } - return nil, false -} - -func ctxDialer() func(context.Context, string) (net.Conn, error) { - l := bufconn.Listen(0) - return func(ctx context.Context, _ string) (net.Conn, error) { - return l.DialContext(ctx) - } -} - -func eventAttrMap(events []trace.Event) []map[attribute.Key]attribute.Value { - maps := make([]map[attribute.Key]attribute.Value, len(events)) - for i, event := range events { - maps[i] = make(map[attribute.Key]attribute.Value, len(event.Attributes)) - for _, a := range event.Attributes { - maps[i][a.Key] = a.Value - } - } - return maps -} - -type mockClientStream struct { - Desc *grpc.StreamDesc - Ctx context.Context - msgs []grpc_testing.SimpleResponse -} - -func (mockClientStream) SendMsg(m any) error { return nil } -func (c *mockClientStream) RecvMsg(m any) error { - if len(c.msgs) == 0 { - return io.EOF - } - c.msgs = c.msgs[1:] - return nil -} -func (mockClientStream) CloseSend() error { return nil } -func (c mockClientStream) Context() context.Context { return c.Ctx } -func (mockClientStream) Header() (metadata.MD, error) { return nil, nil } -func (mockClientStream) Trailer() metadata.MD { return nil } - -type clientStreamOpts struct { - NumRecvMsgs int - DisableServerStreams bool - Events []otelgrpc.Event -} - -func newMockClientStream(opts clientStreamOpts) *mockClientStream { - var msgs []grpc_testing.SimpleResponse - for i := 0; i < opts.NumRecvMsgs; i++ { - msgs = append(msgs, grpc_testing.SimpleResponse{}) - } - return &mockClientStream{msgs: msgs} -} - -func createInterceptedStreamClient(t *testing.T, method string, opts clientStreamOpts) (grpc.ClientStream, *tracetest.SpanRecorder) { - mockStream := newMockClientStream(opts) - clientConn, err := grpc.NewClient("fake:8906", - grpc.WithContextDialer(ctxDialer()), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - if err != nil { - t.Fatalf("failed to create client connection: %v", err) - } - defer clientConn.Close() - - // tracer - sr := tracetest.NewSpanRecorder() - tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) - interceptorOpts := []otelgrpc.Option{ - otelgrpc.WithTracerProvider(tp), - otelgrpc.WithSpanOptions(oteltrace.WithAttributes(attribute.Bool("custom", true))), - } - if len(opts.Events) > 0 { - interceptorOpts = append(interceptorOpts, otelgrpc.WithMessageEvents(opts.Events...)) - } - //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. - streamCI := otelgrpc.StreamClientInterceptor(interceptorOpts...) - - streamClient, err := streamCI( - context.Background(), - &grpc.StreamDesc{ServerStreams: !opts.DisableServerStreams}, - clientConn, - method, - func(ctx context.Context, - desc *grpc.StreamDesc, - cc *grpc.ClientConn, - method string, - opts ...grpc.CallOption, - ) (grpc.ClientStream, error) { - mockStream.Desc = desc - mockStream.Ctx = ctx - return mockStream, nil - }, - ) - require.NoError(t, err, "initialize grpc stream client") - return streamClient, sr -} - -func TestStreamClientInterceptorOnBIDIStream(t *testing.T) { - defer goleak.VerifyNone(t) - - method := "/github.com.serviceName/bar" - name := "github.com.serviceName/bar" - opts := clientStreamOpts{ - NumRecvMsgs: 10, - Events: []otelgrpc.Event{otelgrpc.SentEvents, otelgrpc.ReceivedEvents}, - } - streamClient, sr := createInterceptedStreamClient(t, method, opts) - _, ok := getSpanFromRecorder(sr, name) - require.False(t, ok, "span should not end while stream is open") - - req := &grpc_testing.SimpleRequest{} - reply := &grpc_testing.SimpleResponse{} - - // send and receive fake data - for i := 0; i < 10; i++ { - _ = streamClient.SendMsg(req) - _ = streamClient.RecvMsg(reply) - } - - // The stream has been exhausted so next read should get a EOF and the stream should be considered closed. - err := streamClient.RecvMsg(reply) - require.Equal(t, io.EOF, err) - - // wait for span end that is called in separate go routine - var span trace.ReadOnlySpan - require.Eventually(t, func() bool { - span, ok = getSpanFromRecorder(sr, name) - return ok - }, 5*time.Second, time.Second, "missing span %s", name) - - expectedAttr := []attribute.KeyValue{ - semconv.RPCSystemGRPC, - semconv.RPCGRPCStatusCodeOk, - semconv.RPCService("github.com.serviceName"), - semconv.RPCMethod("bar"), - semconv.ServerAddress("fake"), - semconv.ServerPort(8906), - attribute.Bool("custom", true), - } - assert.ElementsMatch(t, expectedAttr, span.Attributes()) - - events := span.Events() - require.Len(t, events, 20) - for i := 0; i < 20; i += 2 { - msgID := i/2 + 1 - validate := func(eventName string, attrs []attribute.KeyValue) { - for _, kv := range attrs { - k, v := kv.Key, kv.Value - if k == semconv.RPCMessageTypeKey && v.AsString() != eventName { - t.Errorf("invalid event on index: %d expecting %s event, receive %s event", i, eventName, v.AsString()) - } - if k == semconv.RPCMessageIDKey && v != attribute.IntValue(msgID) { - t.Errorf("invalid id for message event expected %d received %d", msgID, v.AsInt64()) - } - } - } - validate("SENT", events[i].Attributes) - validate("RECEIVED", events[i+1].Attributes) - } - - // ensure CloseSend can be subsequently called - _ = streamClient.CloseSend() -} - -func TestStreamClientInterceptorEvents(t *testing.T) { - testCases := []struct { - Name string - Events []otelgrpc.Event - }{ - {Name: "With both events", Events: []otelgrpc.Event{otelgrpc.SentEvents, otelgrpc.ReceivedEvents}}, - {Name: "With only sent events", Events: []otelgrpc.Event{otelgrpc.SentEvents}}, - {Name: "With only received events", Events: []otelgrpc.Event{otelgrpc.ReceivedEvents}}, - {Name: "No events", Events: []otelgrpc.Event{}}, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - defer goleak.VerifyNone(t) - - method := "/github.com.serviceName/bar" - name := "github.com.serviceName/bar" - streamClient, sr := createInterceptedStreamClient(t, method, clientStreamOpts{NumRecvMsgs: 1, Events: testCase.Events}) - _, ok := getSpanFromRecorder(sr, name) - require.False(t, ok, "span should not end while stream is open") - - req := &grpc_testing.SimpleRequest{} - reply := &grpc_testing.SimpleResponse{} - var eventsAttr []map[attribute.Key]attribute.Value - - // send and receive fake data - _ = streamClient.SendMsg(req) - _ = streamClient.RecvMsg(reply) - for _, event := range testCase.Events { - switch event { - case otelgrpc.SentEvents: - eventsAttr = append(eventsAttr, - map[attribute.Key]attribute.Value{ - semconv.RPCMessageTypeKey: attribute.StringValue("SENT"), - semconv.RPCMessageIDKey: attribute.IntValue(1), - }, - ) - case otelgrpc.ReceivedEvents: - eventsAttr = append(eventsAttr, - map[attribute.Key]attribute.Value{ - semconv.RPCMessageTypeKey: attribute.StringValue("RECEIVED"), - semconv.RPCMessageIDKey: attribute.IntValue(1), - }, - ) - } - } - - // The stream has been exhausted so next read should get a EOF and the stream should be considered closed. - err := streamClient.RecvMsg(reply) - require.Equal(t, io.EOF, err) - - // wait for span end that is called in separate go routine - var span trace.ReadOnlySpan - require.Eventually(t, func() bool { - span, ok = getSpanFromRecorder(sr, name) - return ok - }, 5*time.Second, time.Second, "missing span %s", name) - - if len(testCase.Events) == 0 { - assert.Empty(t, span.Events()) - } else { - assert.Len(t, span.Events(), len(eventsAttr)) - assert.Equal(t, eventsAttr, eventAttrMap(span.Events())) - } - - // ensure CloseSend can be subsequently called - _ = streamClient.CloseSend() - }) - } -} - -func TestStreamClientInterceptorOnUnidirectionalClientServerStream(t *testing.T) { - defer goleak.VerifyNone(t) - - method := "/github.com.serviceName/bar" - name := "github.com.serviceName/bar" - opts := clientStreamOpts{ - NumRecvMsgs: 1, - DisableServerStreams: true, - Events: []otelgrpc.Event{otelgrpc.ReceivedEvents, otelgrpc.SentEvents}, - } - streamClient, sr := createInterceptedStreamClient(t, method, opts) - _, ok := getSpanFromRecorder(sr, name) - require.False(t, ok, "span should not end while stream is open") - - req := &grpc_testing.SimpleRequest{} - reply := &grpc_testing.SimpleResponse{} - - // send fake data - for i := 0; i < 10; i++ { - _ = streamClient.SendMsg(req) - } - - // A real user would call CloseAndRecv() on the generated client which would generate a sequence of CloseSend() - // and RecvMsg() calls. - _ = streamClient.CloseSend() - err := streamClient.RecvMsg(reply) - require.NoError(t, err) - - // wait for span end that is called in separate go routine - var span trace.ReadOnlySpan - require.Eventually(t, func() bool { - span, ok = getSpanFromRecorder(sr, name) - return ok - }, 5*time.Second, time.Second, "missing span %s", name) - - expectedAttr := []attribute.KeyValue{ - semconv.RPCSystemGRPC, - semconv.RPCGRPCStatusCodeOk, - semconv.RPCService("github.com.serviceName"), - semconv.RPCMethod("bar"), - semconv.ServerAddress("fake"), - semconv.ServerPort(8906), - attribute.Bool("custom", true), - } - assert.ElementsMatch(t, expectedAttr, span.Attributes()) - - // Note that there's no "RECEIVED" event generated for the server response. This is a bug. - events := span.Events() - require.Len(t, events, 10) - for i := 0; i < 10; i++ { - msgID := i + 1 - validate := func(eventName string, attrs []attribute.KeyValue) { - for _, kv := range attrs { - k, v := kv.Key, kv.Value - if k == semconv.RPCMessageTypeKey && v.AsString() != eventName { - t.Errorf("invalid event on index: %d expecting %s event, receive %s event", i, eventName, v.AsString()) - } - if k == semconv.RPCMessageIDKey && v != attribute.IntValue(msgID) { - t.Errorf("invalid id for message event expected %d received %d", msgID, v.AsInt64()) - } - } - } - validate("SENT", events[i].Attributes) - } -} - -// TestStreamClientInterceptorCancelContext tests a cancel context situation. -// There should be no goleaks. -func TestStreamClientInterceptorCancelContext(t *testing.T) { - defer goleak.VerifyNone(t) - - clientConn, err := grpc.NewClient("fake:8906", - grpc.WithContextDialer(ctxDialer()), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - if err != nil { - t.Fatalf("failed to create client connection: %v", err) - } - defer clientConn.Close() - - // tracer - sr := tracetest.NewSpanRecorder() - tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) - //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. - streamCI := otelgrpc.StreamClientInterceptor( - otelgrpc.WithTracerProvider(tp), - otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), - ) - - var mockClStr *mockClientStream - method := "/github.com.serviceName/bar" - name := "github.com.serviceName/bar" - - // create a context with cancel - cancelCtx, cancel := context.WithCancel(context.Background()) - defer cancel() - streamClient, err := streamCI( - cancelCtx, - &grpc.StreamDesc{ServerStreams: true}, - clientConn, - method, - func(ctx context.Context, - desc *grpc.StreamDesc, - cc *grpc.ClientConn, - method string, - opts ...grpc.CallOption, - ) (grpc.ClientStream, error) { - mockClStr = &mockClientStream{Desc: desc, Ctx: ctx} - return mockClStr, nil - }, - ) - require.NoError(t, err, "initialize grpc stream client") - _, ok := getSpanFromRecorder(sr, name) - require.False(t, ok, "span should not ended while stream is open") - - req := &grpc_testing.SimpleRequest{} - reply := &grpc_testing.SimpleResponse{} - - // send and receive fake data - for i := 0; i < 10; i++ { - _ = streamClient.SendMsg(req) - _ = streamClient.RecvMsg(reply) - } - - // close client stream - _ = streamClient.CloseSend() -} - -// TestStreamClientInterceptorWithError tests a situation that streamer returns an error. -func TestStreamClientInterceptorWithError(t *testing.T) { - defer goleak.VerifyNone(t) - - clientConn, err := grpc.NewClient("fake:8906", - grpc.WithContextDialer(ctxDialer()), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - if err != nil { - t.Fatalf("failed to create client connection: %v", err) - } - defer clientConn.Close() - - // tracer - sr := tracetest.NewSpanRecorder() - tp := trace.NewTracerProvider(trace.WithSpanProcessor(sr)) - //nolint:staticcheck // Interceptors are deprecated and will be removed in the next release. - streamCI := otelgrpc.StreamClientInterceptor( - otelgrpc.WithTracerProvider(tp), - otelgrpc.WithMessageEvents(otelgrpc.ReceivedEvents, otelgrpc.SentEvents), - ) - - var mockClStr *mockClientStream - method := "/github.com.serviceName/bar" - name := "github.com.serviceName/bar" - - streamClient, err := streamCI( - context.Background(), - &grpc.StreamDesc{ServerStreams: true}, - clientConn, - method, - func(ctx context.Context, - desc *grpc.StreamDesc, - cc *grpc.ClientConn, - method string, - opts ...grpc.CallOption, - ) (grpc.ClientStream, error) { - mockClStr = &mockClientStream{Desc: desc, Ctx: ctx} - return mockClStr, errors.New("test") - }, - ) - require.Error(t, err, "initialize grpc stream client") - assert.IsType(t, &mockClientStream{}, streamClient) - - span, ok := getSpanFromRecorder(sr, name) - require.True(t, ok, "missing span %s", name) - - expectedAttr := []attribute.KeyValue{ - semconv.RPCSystemGRPC, - semconv.RPCGRPCStatusCodeUnknown, - semconv.RPCService("github.com.serviceName"), - semconv.RPCMethod("bar"), - semconv.ServerAddress("fake"), - semconv.ServerPort(8906), - } - assert.ElementsMatch(t, expectedAttr, span.Attributes()) - assert.Equal(t, codes.Error, span.Status().Code) -} - -var serverChecks = []struct { - grpcCode grpc_codes.Code - wantSpanCode codes.Code - wantSpanStatusDescription string -}{ - { - grpcCode: grpc_codes.OK, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.Canceled, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.Unknown, - wantSpanCode: codes.Error, - wantSpanStatusDescription: grpc_codes.Unknown.String(), - }, - { - grpcCode: grpc_codes.InvalidArgument, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.DeadlineExceeded, - wantSpanCode: codes.Error, - wantSpanStatusDescription: grpc_codes.DeadlineExceeded.String(), - }, - { - grpcCode: grpc_codes.NotFound, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.AlreadyExists, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.PermissionDenied, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.ResourceExhausted, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.FailedPrecondition, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.Aborted, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.OutOfRange, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, - { - grpcCode: grpc_codes.Unimplemented, - wantSpanCode: codes.Error, - wantSpanStatusDescription: grpc_codes.Unimplemented.String(), - }, - { - grpcCode: grpc_codes.Internal, - wantSpanCode: codes.Error, - wantSpanStatusDescription: grpc_codes.Internal.String(), - }, - { - grpcCode: grpc_codes.Unavailable, - wantSpanCode: codes.Error, - wantSpanStatusDescription: grpc_codes.Unavailable.String(), - }, - { - grpcCode: grpc_codes.DataLoss, - wantSpanCode: codes.Error, - wantSpanStatusDescription: grpc_codes.DataLoss.String(), - }, - { - grpcCode: grpc_codes.Unauthenticated, - wantSpanCode: codes.Unset, - wantSpanStatusDescription: "", - }, -} - -func assertServerSpan(t *testing.T, wantSpanCode codes.Code, wantSpanStatusDescription string, wantGrpcCode grpc_codes.Code, span trace.ReadOnlySpan) { - // validate span status - assert.Equal(t, wantSpanCode, span.Status().Code) - assert.Equal(t, wantSpanStatusDescription, span.Status().Description) - - // validate grpc code span attribute - var codeAttr attribute.KeyValue - for _, a := range span.Attributes() { - if a.Key == semconv.RPCGRPCStatusCodeKey { - codeAttr = a - break - } - } - - require.True(t, codeAttr.Valid(), "attributes contain gRPC status code") - assert.Equal(t, attribute.Int64Value(int64(wantGrpcCode)), codeAttr.Value) -} diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handlertest_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handlertest_test.go index 6fb1dc989a4..5e46749d16f 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handlertest_test.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handlertest_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" + otelcode "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" @@ -17,13 +18,114 @@ import ( "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.34.0" "go.opentelemetry.io/otel/semconv/v1.34.0/rpcconv" - "google.golang.org/grpc/codes" + grpc_codes "google.golang.org/grpc/codes" "google.golang.org/grpc/stats" "google.golang.org/grpc/status" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" ) +func getSpanFromRecorder(sr *tracetest.SpanRecorder, name string) (trace.ReadOnlySpan, bool) { + for _, s := range sr.Ended() { + if s.Name() == name { + return s, true + } + } + return nil, false +} + +var serverChecks = []struct { + grpcCode grpc_codes.Code + wantSpanCode otelcode.Code + wantSpanStatusDescription string +}{ + { + grpcCode: grpc_codes.OK, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.Canceled, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.Unknown, + wantSpanCode: otelcode.Error, + wantSpanStatusDescription: grpc_codes.Unknown.String(), + }, + { + grpcCode: grpc_codes.InvalidArgument, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.DeadlineExceeded, + wantSpanCode: otelcode.Error, + wantSpanStatusDescription: grpc_codes.DeadlineExceeded.String(), + }, + { + grpcCode: grpc_codes.NotFound, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.AlreadyExists, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.PermissionDenied, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.ResourceExhausted, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.FailedPrecondition, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.Aborted, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.OutOfRange, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, + { + grpcCode: grpc_codes.Unimplemented, + wantSpanCode: otelcode.Error, + wantSpanStatusDescription: grpc_codes.Unimplemented.String(), + }, + { + grpcCode: grpc_codes.Internal, + wantSpanCode: otelcode.Error, + wantSpanStatusDescription: grpc_codes.Internal.String(), + }, + { + grpcCode: grpc_codes.Unavailable, + wantSpanCode: otelcode.Error, + wantSpanStatusDescription: grpc_codes.Unavailable.String(), + }, + { + grpcCode: grpc_codes.DataLoss, + wantSpanCode: otelcode.Error, + wantSpanStatusDescription: grpc_codes.DataLoss.String(), + }, + { + grpcCode: grpc_codes.Unauthenticated, + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + }, +} + func TestStatsHandlerHandleRPCServerErrors(t *testing.T) { for _, check := range serverChecks { name := check.grpcCode.String() @@ -65,7 +167,25 @@ func TestStatsHandlerHandleRPCServerErrors(t *testing.T) { } } -func assertStatsHandlerServerMetrics(t *testing.T, reader metric.Reader, serviceName, name string, code codes.Code) { +func assertServerSpan(t *testing.T, wantSpanCode otelcode.Code, wantSpanStatusDescription string, wantGrpcCode grpc_codes.Code, span trace.ReadOnlySpan) { + // validate span status + assert.Equal(t, wantSpanCode, span.Status().Code) + assert.Equal(t, wantSpanStatusDescription, span.Status().Description) + + // validate grpc code span attribute + var codeAttr attribute.KeyValue + for _, a := range span.Attributes() { + if a.Key == semconv.RPCGRPCStatusCodeKey { + codeAttr = a + break + } + } + + require.True(t, codeAttr.Valid(), "attributes contain gRPC status code") + assert.Equal(t, attribute.Int64Value(int64(wantGrpcCode)), codeAttr.Value) +} + +func assertStatsHandlerServerMetrics(t *testing.T, reader metric.Reader, serviceName, name string, code grpc_codes.Code) { want := metricdata.ScopeMetrics{ Scope: wantInstrumentationScope, Metrics: []metricdata.Metrics{ From c9909d4cb7c39321a48b86c301fb8a106a4e9f4b Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 31 Jul 2025 12:10:29 -0700 Subject: [PATCH 2/7] Add a changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92f86cde3f2..9116374fb34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` +- The deprecated `StreamClientInterceptor` function from `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` is removed. (#7646) ### Fixed From a7ed8556273d9df0b27369168703ca445c59de52 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 31 Jul 2025 12:21:18 -0700 Subject: [PATCH 3/7] Remove unused tracerProvider var --- .../google.golang.org/grpc/otelgrpc/benchmark_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/benchmark_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/benchmark_test.go index 92a5cd4a999..2a0e9610a3d 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/benchmark_test.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/benchmark_test.go @@ -8,7 +8,6 @@ import ( "net" "testing" - "go.opentelemetry.io/otel/trace/noop" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" pb "google.golang.org/grpc/interop/grpc_testing" @@ -19,8 +18,6 @@ import ( const bufSize = 2048 -var tracerProvider = noop.NewTracerProvider() - func benchmark(b *testing.B, cOpt []grpc.DialOption, sOpt []grpc.ServerOption) { l := bufconn.Listen(bufSize) defer l.Close() From debb618f66bf1e49d581afc32a296855b6dee69c Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 31 Jul 2025 12:22:06 -0700 Subject: [PATCH 4/7] Remove unused checkStreamClientSpans --- .../grpc/otelgrpc/grpc_test.go | 174 ------------------ 1 file changed, 174 deletions(-) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/grpc_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/grpc_test.go index b453cbfec65..6c73b497b81 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/grpc_test.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/grpc_test.go @@ -6,7 +6,6 @@ package otelgrpc_test import ( "context" "net" - "strconv" "testing" "github.com/stretchr/testify/assert" @@ -72,179 +71,6 @@ func doCalls(ctx context.Context, client pb.TestServiceClient) { test.DoPingPong(ctx, client) } -func checkStreamClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) { - require.Len(t, spans, 3) - - host, p, err := net.SplitHostPort(addr) - require.NoError(t, err) - port, err := strconv.Atoi(p) - require.NoError(t, err) - - streamInput := spans[0] - assert.False(t, streamInput.EndTime().IsZero()) - assert.Equal(t, "grpc.testing.TestService/StreamingInputCall", streamInput.Name()) - // sizes from reqSizes in "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test". - assertEvents(t, []trace.Event{ - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(1), - semconv.RPCMessageTypeKey.String("SENT"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(2), - semconv.RPCMessageTypeKey.String("SENT"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(3), - semconv.RPCMessageTypeKey.String("SENT"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(4), - semconv.RPCMessageTypeKey.String("SENT"), - }, - }, - // client does not record an event for the server response. - }, streamInput.Events()) - assert.ElementsMatch(t, []attribute.KeyValue{ - semconv.RPCMethod("StreamingInputCall"), - semconv.RPCService("grpc.testing.TestService"), - semconv.RPCSystemGRPC, - semconv.RPCGRPCStatusCodeOk, - semconv.ServerAddress(host), - semconv.ServerPort(port), - }, streamInput.Attributes()) - - streamOutput := spans[1] - assert.False(t, streamOutput.EndTime().IsZero()) - assert.Equal(t, "grpc.testing.TestService/StreamingOutputCall", streamOutput.Name()) - // sizes from respSizes in "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/test". - assertEvents(t, []trace.Event{ - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(1), - semconv.RPCMessageTypeKey.String("SENT"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(1), - semconv.RPCMessageTypeKey.String("RECEIVED"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(2), - semconv.RPCMessageTypeKey.String("RECEIVED"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(3), - semconv.RPCMessageTypeKey.String("RECEIVED"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(4), - semconv.RPCMessageTypeKey.String("RECEIVED"), - }, - }, - }, streamOutput.Events()) - assert.ElementsMatch(t, []attribute.KeyValue{ - semconv.RPCMethod("StreamingOutputCall"), - semconv.RPCService("grpc.testing.TestService"), - semconv.RPCSystemGRPC, - semconv.RPCGRPCStatusCodeOk, - semconv.ServerAddress(host), - semconv.ServerPort(port), - }, streamOutput.Attributes()) - - pingPong := spans[2] - assert.False(t, pingPong.EndTime().IsZero()) - assert.Equal(t, "grpc.testing.TestService/FullDuplexCall", pingPong.Name()) - assertEvents(t, []trace.Event{ - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(1), - semconv.RPCMessageTypeKey.String("SENT"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(1), - semconv.RPCMessageTypeKey.String("RECEIVED"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(2), - semconv.RPCMessageTypeKey.String("SENT"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(2), - semconv.RPCMessageTypeKey.String("RECEIVED"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(3), - semconv.RPCMessageTypeKey.String("SENT"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(3), - semconv.RPCMessageTypeKey.String("RECEIVED"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(4), - semconv.RPCMessageTypeKey.String("SENT"), - }, - }, - { - Name: "message", - Attributes: []attribute.KeyValue{ - semconv.RPCMessageIDKey.Int(4), - semconv.RPCMessageTypeKey.String("RECEIVED"), - }, - }, - }, pingPong.Events()) - assert.ElementsMatch(t, []attribute.KeyValue{ - semconv.RPCMethod("FullDuplexCall"), - semconv.RPCService("grpc.testing.TestService"), - semconv.RPCSystemGRPC, - semconv.RPCGRPCStatusCodeOk, - semconv.ServerAddress(host), - semconv.ServerPort(port), - }, pingPong.Attributes()) -} - func assertEvents(t *testing.T, expected, actual []trace.Event) bool { //nolint:unparam if !assert.Len(t, actual, len(expected)) { return false From 8b9490df1b1eadccadf72072402dd3e9989793ae Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 31 Jul 2025 12:23:38 -0700 Subject: [PATCH 5/7] Remove unused ClientStream type an related decls --- .../grpc/otelgrpc/interceptor.go | 120 ------------------ 1 file changed, 120 deletions(-) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go index de060beedbd..f371f8c106b 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go @@ -6,138 +6,18 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g // gRPC tracing middleware // https://opentelemetry.io/docs/specs/semconv/rpc/ import ( - "context" - "errors" - "io" "net" "strconv" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.34.0" - "go.opentelemetry.io/otel/trace" - "google.golang.org/grpc" grpc_codes "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal" ) -type messageType attribute.KeyValue - -// Event adds an event of the messageType to the span associated with the -// passed context with a message id. -func (m messageType) Event(ctx context.Context, id int, _ any) { - span := trace.SpanFromContext(ctx) - if !span.IsRecording() { - return - } - span.AddEvent("message", trace.WithAttributes( - attribute.KeyValue(m), - semconv.RPCMessageIDKey.Int(id), - )) -} - -var ( - messageSent = messageType(semconv.RPCMessageTypeSent) - messageReceived = messageType(semconv.RPCMessageTypeReceived) -) - -// clientStream wraps around the embedded grpc.ClientStream, and intercepts the RecvMsg and -// SendMsg method call. -type clientStream struct { - grpc.ClientStream - desc *grpc.StreamDesc - - span trace.Span - - receivedEvent bool - sentEvent bool - - receivedMessageID int - sentMessageID int -} - -var _ = proto.Marshal - -func (w *clientStream) RecvMsg(m any) error { - err := w.ClientStream.RecvMsg(m) - switch { - case err == nil && !w.desc.ServerStreams: - w.endSpan(nil) - case errors.Is(err, io.EOF): - w.endSpan(nil) - case err != nil: - w.endSpan(err) - default: - w.receivedMessageID++ - - if w.receivedEvent { - messageReceived.Event(w.Context(), w.receivedMessageID, m) - } - } - - return err -} - -func (w *clientStream) SendMsg(m any) error { - err := w.ClientStream.SendMsg(m) - - w.sentMessageID++ - - if w.sentEvent { - messageSent.Event(w.Context(), w.sentMessageID, m) - } - - if err != nil { - w.endSpan(err) - } - - return err -} - -func (w *clientStream) Header() (metadata.MD, error) { - md, err := w.ClientStream.Header() - if err != nil { - w.endSpan(err) - } - - return md, err -} - -func (w *clientStream) CloseSend() error { - err := w.ClientStream.CloseSend() - if err != nil { - w.endSpan(err) - } - - return err -} - -func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc, span trace.Span, cfg *config) *clientStream { - return &clientStream{ - ClientStream: s, - span: span, - desc: desc, - receivedEvent: cfg.ReceivedEvent, - sentEvent: cfg.SentEvent, - } -} - -func (w *clientStream) endSpan(err error) { - if err != nil { - s, _ := status.FromError(err) - w.span.SetStatus(codes.Error, s.Message()) - w.span.SetAttributes(statusCodeAttr(s.Code())) - } else { - w.span.SetAttributes(statusCodeAttr(grpc_codes.OK)) - } - - w.span.End() -} - // telemetryAttributes returns a span name and span and metric attributes from // the gRPC method and peer address. func telemetryAttributes(fullMethod, serverAddr string) (string, []attribute.KeyValue) { From f91f2ee18a214e37ff1e3543cbe01a3fa5674893 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 31 Jul 2025 12:24:45 -0700 Subject: [PATCH 6/7] Remove unused telemetryAttributes func --- .../grpc/otelgrpc/interceptor.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go index f371f8c106b..3c0a14a570a 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go @@ -14,23 +14,8 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.34.0" grpc_codes "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - - "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal" ) -// telemetryAttributes returns a span name and span and metric attributes from -// the gRPC method and peer address. -func telemetryAttributes(fullMethod, serverAddr string) (string, []attribute.KeyValue) { - name, methodAttrs := internal.ParseFullMethod(fullMethod) - srvAttrs := serverAddrAttrs(serverAddr) - - attrs := make([]attribute.KeyValue, 0, 1+len(methodAttrs)+len(srvAttrs)) - attrs = append(attrs, semconv.RPCSystemGRPC) - attrs = append(attrs, methodAttrs...) - attrs = append(attrs, srvAttrs...) - return name, attrs -} - // serverAddrAttrs returns the server address attributes for the hostport. func serverAddrAttrs(hostport string) []attribute.KeyValue { h, pStr, err := net.SplitHostPort(hostport) From f05d4569dfd6b50949d1d0ea4b2798d02f567ea4 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 31 Jul 2025 12:24:58 -0700 Subject: [PATCH 7/7] Remove unused statusCodeAttr func --- .../google.golang.org/grpc/otelgrpc/interceptor.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go index 3c0a14a570a..fd62f9885af 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go @@ -33,11 +33,6 @@ func serverAddrAttrs(hostport string) []attribute.KeyValue { } } -// statusCodeAttr returns status code attribute based on given gRPC code. -func statusCodeAttr(c grpc_codes.Code) attribute.KeyValue { - return semconv.RPCGRPCStatusCodeKey.Int64(int64(c)) -} - // serverStatus returns a span status code and message for a given gRPC // status code. It maps specific gRPC status codes to a corresponding span // status code and message. This function is intended for use on the server