Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package otelgrpc // import "go.opentelemetry.io/contrib/instrumentation/google.g

import (
"context"
"strconv"
"sync/atomic"
"time"

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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"
Copy link
Copy Markdown

@bincyber bincyber Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not this be spelled the same way as grpc_codes.Canceled? @haines

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string representation of this error does have two L in the grpc spec.

https://github.com/grpc/grpc/blob/v1.75.0/doc/statuscodes.md

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this is what is used as the canonical string internally in the Go gRPC implementation as well, unfortunately they named the constant using the alternate spelling which makes things confusing: https://github.com/grpc/grpc-go/blob/b71c26202050d6a6c528e42f96a2712baa74eb61/codes/code_string.go#L77

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:
Comment thread
pellared marked this conversation as resolved.
return "CODE(" + strconv.FormatInt(int64(code), 10) + ")"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
},
}

Expand Down Expand Up @@ -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)
Expand All @@ -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{
Expand All @@ -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,
),
},
Expand Down
Loading