diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a13a07218..31f38e43480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ The next release will require at least [Go 1.25]. ### Fixed - Change the `rpc.server.call.duration` metric value from milliseconds to seconds in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#8509) +- Change the `rpc.response.status_code` attribute to the canonical `UPPER_SNAKE_CASE` format in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#8565) - Enforce that `client_certificate_file` and `client_key_file` are provided together in `go.opentelemetry.io/contrib/otelconf`. (#8450) - Fixed broken CSS and JavaScript CDN URLs in `go.opentelemetry.io/contrib/zpages` by replacing the inaccessible code.getmdl.io CDN with cdnjs.cloudflare.com. (#8502) - Use Prometheus translation strategy instead of deprecated funcs in `go.opentelemetry.io/contrib/otelconf`. (#8595) diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go index 9e1820d7832..4254b250725 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go @@ -5,6 +5,7 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g import ( "context" + "strconv" "sync/atomic" "time" @@ -328,9 +329,9 @@ func (c *config) handleRPC( var s *status.Status if rs.Error != nil { s, _ = status.FromError(rs.Error) - rpcStatusAttr = semconv.RPCResponseStatusCode(s.Code().String()) + rpcStatusAttr = semconv.RPCResponseStatusCode(canonicalString(s.Code())) } else { - rpcStatusAttr = semconv.RPCResponseStatusCode(grpc_codes.OK.String()) + rpcStatusAttr = semconv.RPCResponseStatusCode(canonicalString(grpc_codes.OK)) } if span.IsRecording() { if s != nil { @@ -362,3 +363,44 @@ func (c *config) handleRPC( return } } + +func canonicalString(code grpc_codes.Code) string { + switch code { + case grpc_codes.OK: + return "OK" + case grpc_codes.Canceled: + return "CANCELLED" + case grpc_codes.Unknown: + return "UNKNOWN" + case grpc_codes.InvalidArgument: + return "INVALID_ARGUMENT" + case grpc_codes.DeadlineExceeded: + return "DEADLINE_EXCEEDED" + case grpc_codes.NotFound: + return "NOT_FOUND" + case grpc_codes.AlreadyExists: + return "ALREADY_EXISTS" + case grpc_codes.PermissionDenied: + return "PERMISSION_DENIED" + case grpc_codes.ResourceExhausted: + return "RESOURCE_EXHAUSTED" + case grpc_codes.FailedPrecondition: + return "FAILED_PRECONDITION" + case grpc_codes.Aborted: + return "ABORTED" + case grpc_codes.OutOfRange: + return "OUT_OF_RANGE" + case grpc_codes.Unimplemented: + return "UNIMPLEMENTED" + case grpc_codes.Internal: + return "INTERNAL" + case grpc_codes.Unavailable: + return "UNAVAILABLE" + case grpc_codes.DataLoss: + return "DATA_LOSS" + case grpc_codes.Unauthenticated: + return "UNAUTHENTICATED" + default: + return "CODE(" + strconv.FormatInt(int64(code), 10) + ")" + } +} 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 3cce365ebd5..c606df60205 100644 --- a/instrumentation/google.golang.org/grpc/otelgrpc/stats_handlertest_test.go +++ b/instrumentation/google.golang.org/grpc/otelgrpc/stats_handlertest_test.go @@ -37,91 +37,115 @@ var serverChecks = []struct { grpcCode grpc_codes.Code wantSpanCode otelcode.Code wantSpanStatusDescription string + wantRPCResponseStatusCode string }{ { grpcCode: grpc_codes.OK, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "OK", }, { grpcCode: grpc_codes.Canceled, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "CANCELLED", }, { grpcCode: grpc_codes.Unknown, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.Unknown.String(), + wantRPCResponseStatusCode: "UNKNOWN", }, { grpcCode: grpc_codes.InvalidArgument, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "INVALID_ARGUMENT", }, { grpcCode: grpc_codes.DeadlineExceeded, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.DeadlineExceeded.String(), + wantRPCResponseStatusCode: "DEADLINE_EXCEEDED", }, { grpcCode: grpc_codes.NotFound, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "NOT_FOUND", }, { grpcCode: grpc_codes.AlreadyExists, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "ALREADY_EXISTS", }, { grpcCode: grpc_codes.PermissionDenied, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "PERMISSION_DENIED", }, { grpcCode: grpc_codes.ResourceExhausted, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "RESOURCE_EXHAUSTED", }, { grpcCode: grpc_codes.FailedPrecondition, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "FAILED_PRECONDITION", }, { grpcCode: grpc_codes.Aborted, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "ABORTED", }, { grpcCode: grpc_codes.OutOfRange, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "OUT_OF_RANGE", }, { grpcCode: grpc_codes.Unimplemented, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.Unimplemented.String(), + wantRPCResponseStatusCode: "UNIMPLEMENTED", }, { grpcCode: grpc_codes.Internal, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.Internal.String(), + wantRPCResponseStatusCode: "INTERNAL", }, { grpcCode: grpc_codes.Unavailable, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.Unavailable.String(), + wantRPCResponseStatusCode: "UNAVAILABLE", }, { grpcCode: grpc_codes.DataLoss, wantSpanCode: otelcode.Error, wantSpanStatusDescription: grpc_codes.DataLoss.String(), + wantRPCResponseStatusCode: "DATA_LOSS", }, { grpcCode: grpc_codes.Unauthenticated, wantSpanCode: otelcode.Unset, wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "UNAUTHENTICATED", + }, + { + grpcCode: grpc_codes.Code(9999), + wantSpanCode: otelcode.Unset, + wantSpanStatusDescription: "", + wantRPCResponseStatusCode: "CODE(9999)", }, } @@ -158,15 +182,15 @@ func TestStatsHandlerHandleRPCServerErrors(t *testing.T) { // validate span span, ok := getSpanFromRecorder(sr, methodName) require.True(t, ok, "missing span %s", methodName) - assertServerSpan(t, check.wantSpanCode, check.wantSpanStatusDescription, check.grpcCode, span) + assertServerSpan(t, check.wantSpanCode, check.wantSpanStatusDescription, check.wantRPCResponseStatusCode, span) // validate metric - assertStatsHandlerServerMetrics(t, mr, serviceName, name, check.grpcCode) + assertStatsHandlerServerMetrics(t, mr, serviceName, name, check.wantRPCResponseStatusCode) }) } } -func assertServerSpan(t *testing.T, wantSpanCode otelcode.Code, wantSpanStatusDescription string, wantGrpcCode grpc_codes.Code, span trace.ReadOnlySpan) { +func assertServerSpan(t *testing.T, wantSpanCode otelcode.Code, wantSpanStatusDescription, wantGrpcCode string, span trace.ReadOnlySpan) { // validate span status assert.Equal(t, wantSpanCode, span.Status().Code) assert.Equal(t, wantSpanStatusDescription, span.Status().Description) @@ -181,10 +205,10 @@ func assertServerSpan(t *testing.T, wantSpanCode otelcode.Code, wantSpanStatusDe } require.True(t, codeAttr.Valid(), "attributes contain gRPC status code") - assert.Equal(t, attribute.StringValue(wantGrpcCode.String()), codeAttr.Value) + assert.Equal(t, attribute.StringValue(wantGrpcCode), codeAttr.Value) } -func assertStatsHandlerServerMetrics(t *testing.T, reader metric.Reader, serviceName, name string, code grpc_codes.Code) { +func assertStatsHandlerServerMetrics(t *testing.T, reader metric.Reader, serviceName, name, code string) { want := metricdata.ScopeMetrics{ Scope: wantInstrumentationScope, Metrics: []metricdata.Metrics{ @@ -199,7 +223,7 @@ func assertStatsHandlerServerMetrics(t *testing.T, reader metric.Reader, service Attributes: attribute.NewSet( semconv.RPCMethod(serviceName+"/"+name), semconv.RPCSystemNameGRPC, - semconv.RPCResponseStatusCode(code.String()), + semconv.RPCResponseStatusCode(code), testMetricAttr, ), },