Skip to content
Closed
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 @@ -22,6 +22,7 @@ The next release will require at least [Go 1.25].

### Fixed

- Fix `rpc.response.status_code` attribute in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` to use SCREAMING_SNAKE_CASE (e.g. `DEADLINE_EXCEEDED`) as required by the OpenTelemetry RPC semantic conventions, instead of PascalCase (e.g. `DeadlineExceeded`). (#8543)
- Change the `rpc.server.call.duration` metric value from milliseconds to seconds in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#8509)
- 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func checkClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/EmptyCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress(host),
semconv.ServerPort(port),
testSpanAttr,
Expand Down Expand Up @@ -208,7 +208,7 @@ func checkClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/UnaryCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress(host),
semconv.ServerPort(port),
testSpanAttr,
Expand Down Expand Up @@ -268,7 +268,7 @@ func checkClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/StreamingInputCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress(host),
semconv.ServerPort(port),
testSpanAttr,
Expand Down Expand Up @@ -327,7 +327,7 @@ func checkClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/StreamingOutputCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress(host),
semconv.ServerPort(port),
testSpanAttr,
Expand Down Expand Up @@ -413,7 +413,7 @@ func checkClientSpans(t *testing.T, spans []trace.ReadOnlySpan, addr string) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/FullDuplexCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress(host),
semconv.ServerPort(port),
testSpanAttr,
Expand Down Expand Up @@ -451,7 +451,7 @@ func checkServerSpans(t *testing.T, spans []trace.ReadOnlySpan) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/EmptyCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress("127.0.0.1"),
port,
testSpanAttr,
Expand Down Expand Up @@ -485,7 +485,7 @@ func checkServerSpans(t *testing.T, spans []trace.ReadOnlySpan) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/UnaryCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress("127.0.0.1"),
port,
testSpanAttr,
Expand Down Expand Up @@ -547,7 +547,7 @@ func checkServerSpans(t *testing.T, spans []trace.ReadOnlySpan) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/StreamingInputCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress("127.0.0.1"),
port,
testSpanAttr,
Expand Down Expand Up @@ -608,7 +608,7 @@ func checkServerSpans(t *testing.T, spans []trace.ReadOnlySpan) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/StreamingOutputCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress("127.0.0.1"),
port,
testSpanAttr,
Expand Down Expand Up @@ -696,7 +696,7 @@ func checkServerSpans(t *testing.T, spans []trace.ReadOnlySpan) {
assert.ElementsMatch(t, []attribute.KeyValue{
semconv.RPCMethodKey.String("grpc.testing.TestService/FullDuplexCall"),
semconv.RPCSystemNameGRPC,
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.ServerAddress("127.0.0.1"),
port,
testSpanAttr,
Expand Down Expand Up @@ -725,35 +725,35 @@ func checkClientMetrics(t *testing.T, reader metric.Reader) {
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/EmptyCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
},
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/UnaryCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
},
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/StreamingInputCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
},
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/StreamingOutputCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
},
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/FullDuplexCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
Expand Down Expand Up @@ -928,35 +928,35 @@ func checkServerMetrics(t *testing.T, reader metric.Reader) {
DataPoints: []metricdata.HistogramDataPoint[float64]{
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/EmptyCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
},
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/UnaryCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
},
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/StreamingInputCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
},
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/StreamingOutputCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
},
{
Attributes: attribute.NewSet(
semconv.RPCResponseStatusCode(codes.OK.String()),
semconv.RPCResponseStatusCode("OK"),
semconv.RPCMethod("grpc.testing.TestService/FullDuplexCall"),
semconv.RPCSystemNameGRPC,
testMetricAttr),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,49 @@ func (h *clientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
)
}

// grpcCodeString converts a gRPC status code to its SCREAMING_SNAKE_CASE string
// representation as required by the OpenTelemetry RPC semantic conventions.
func grpcCodeString(c grpc_codes.Code) string {
switch c {
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 c.String()
}
}

// TagConn can attach some information to the given context.
func (*clientHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
return ctx
Expand Down Expand Up @@ -328,9 +371,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(grpcCodeString(s.Code()))
} else {
rpcStatusAttr = semconv.RPCResponseStatusCode(grpc_codes.OK.String())
rpcStatusAttr = semconv.RPCResponseStatusCode(grpcCodeString(grpc_codes.OK))
}
if span.IsRecording() {
if s != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,49 @@ import (
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
)

// grpcCodeToString mirrors the production grpcCodeString function, converting
// gRPC status codes to SCREAMING_SNAKE_CASE as required by the OTel RPC spec.
func grpcCodeToString(c grpc_codes.Code) string {
switch c {
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 c.String()
}
}

func getSpanFromRecorder(sr *tracetest.SpanRecorder, name string) (trace.ReadOnlySpan, bool) {
for _, s := range sr.Ended() {
if s.Name() == name {
Expand Down Expand Up @@ -181,7 +224,7 @@ 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(grpcCodeToString(wantGrpcCode)), codeAttr.Value)
}

func assertStatsHandlerServerMetrics(t *testing.T, reader metric.Reader, serviceName, name string, code grpc_codes.Code) {
Expand All @@ -199,7 +242,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(grpcCodeToString(code)),
testMetricAttr,
),
},
Expand Down