Stats handlers in otelgrpc use a fixed mapping from gRPC status codes to OTel span status, and the client treats every non-OK code as an error. This was raised in #9189. Overriding this behavior is allowed as per current spec. Check #9189 (comment).
While couple of approaches remain in progress, this issue tracks decision on choice of override option shape and associated trade-offs. At the moment no Otel SDK contrib offers any override option for gRPC.
Proposed Solution
Three mutually exclusive directions under discussion:
1. Full custom callback
otelgrpc.WithSpanStatusFn(func(ctx context.Context, fullMethod string, grpcStatus *status.Status) (codes.Code, string) {
// arbitrary user logic
})
- Max flexibility in terms of defining rules for user
- Replaces the default mapping entirely, forcing callers to re-implement the standard OTel error cases to stay spec-compliant.
- Runs arbitrary user logic on the hot path for every RPC. Has received push-back because of this.
Non representative in declarative config (otelconf), a function value can't be serialized into YAML/JSON config.
- Shape is not compatible with any future declarative config mechanism.
Implementation Example
2. Per-method non-error code mapping
otelgrpc.WithMethodNonErrorCodes(map[string][]codes.Code{
"/pkg.Svc/Lookup": {codes.NotFound},
})
- Method specific mapping of NonErrorCodes.
- Only exact match is supported. No regex matching support for methods. (New)
- Allows the same gRPC code to be treated differently per RPC (e.g.
NotFound is expected on Lookup but should still be an error on Delete).
- Shape is compatible with future declarative config mechanism (data-only).
No Implementation example exists yet.
3. Global non-error codes
otelgrpc.WithNonErrorCodes(codes.NotFound)
- Flat allowlist, applied uniformly to every RPC, the handler process.
- Simplest option, data-shaped, met favorable reviews from Go SIG;
- Shape is compatible with future declarative config mechanism (data-only).
Implementation example
4. Composition of Per Method (2) + Global Non Error Code (3)
Based on feedback from SIG
- Two independent options to configure status code mapping at Per Method and Global level.
- Precedence If Found (Method level mapping > Global mapping > SDK Default)
- Chosen mapping is applied as replacement, not merge with existing mapping
- RPC Metrics must remain untouched, never derived from overridden Span Status.
- The change is symmetric to client/server.
- No caller-supplied reference survives into the instrumentation config.
Prior Art (Generated by Claude)
Survey of gRPC status-to-span-status handling across OpenTelemetry language SDKs (as of 2026-07-29), none currently expose any user-facing override:
| Language |
Server classification |
Override available? |
Go (otelgrpc) |
6-code allowlist → Error, else Unset |
No (this issue) |
Java (grpc-1.6) |
identical 6-code allowlist, via internal GrpcSpanStatusExtractor |
No, the underlying SpanStatusExtractor extension point exists in instrumentation-api but is never exposed publicly by any instrumentation library's builder |
Python (opentelemetry-instrumentation-grpc) |
identical 6-code allowlist |
No, only a method-instrumentation filter |
.NET (opentelemetry-dotnet-contrib) |
identical 6-code allowlist, static switch |
No dedicated hook, only the generic Enrich*(Activity, ...) callback shared by all .NET instrumentations |
JS (opentelemetry-js) |
no allowlist, any non-OK is Error, same as client |
No, only ignoreGrpcMethods |
Ruby (opentelemetry-ruby-contrib, grpc/gruf) |
no code-based logic, relies on SDK default "exception raised inside in_span → Error" |
No, only a server method-ignore list |
| Notably, JS and Ruby don't even implement the server-side SHOULD-allowlist from the gRPC semantic conventions; they treat every non-OK code as an error on both client and server. That drift exists independently of this feature request and is worth keeping in mind when deciding how much cross-language consistency to require before merging an override API in Go. |
|
|
Stats handlers in
otelgrpcuse a fixed mapping from gRPC status codes to OTel span status, and the client treats every non-OKcode as an error. This was raised in #9189. Overriding this behavior is allowed as per current spec. Check #9189 (comment).While couple of approaches remain in progress, this issue tracks decision on choice of override option shape and associated trade-offs. At the moment no Otel SDK contrib offers any override option for gRPC.
Proposed Solution
Three mutually exclusive directions under discussion:
1. Full custom callback
Non representative in declarative config (otelconf), a function value can't be serialized into YAML/JSON config.Implementation Example
2. Per-method non-error code mapping
NotFoundis expected onLookupbut should still be an error onDelete).No Implementation example exists yet.
3. Global non-error codes
Implementation example
4. Composition of Per Method (2) + Global Non Error Code (3)
Prior Art (Generated by Claude)
Survey of gRPC status-to-span-status handling across OpenTelemetry language SDKs (as of 2026-07-29), none currently expose any user-facing override:
otelgrpc)grpc-1.6)GrpcSpanStatusExtractorSpanStatusExtractorextension point exists ininstrumentation-apibut is never exposed publicly by any instrumentation library's builderopentelemetry-instrumentation-grpc)opentelemetry-dotnet-contrib)Enrich*(Activity, ...)callback shared by all .NET instrumentationsopentelemetry-js)ignoreGrpcMethodsopentelemetry-ruby-contrib,grpc/gruf)in_span→ Error"