Leave span status unset for 4xx responses on server spans#17
Merged
Conversation
Per the OTel HTTP semantic conventions, span status for HTTP status codes in the 4xx range must be left unset for SpanKind.SERVER spans. Decide the span status primarily from the status code resolved by echo.ResolveResponseStatus instead of returning Error for any non-nil handler error (e.g. echo.NewHTTPError(400)). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aldas
requested changes
Jul 8, 2026
aldas
left a comment
Contributor
There was a problem hiding this comment.
Looks good, but please remove refences to Echo specifics in extractor file. I would like it to be as general as can be so other people can use copy if needed for things other than Echo related.
Keep extrator.go and extrator_test.go free of Echo-specific mentions so they can be copied for use outside of Echo. The Echo-specific scenarios remain covered in otel_test.go. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
|
@aldas The Echo-specific scenarios (a handler returning |
Contributor
|
I'll do a next release in couple of days. you can try/use it already if you update dependency to specific commit |
Contributor
|
this is now in lastest release. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Thank you for maintaining this middleware!
The middleware currently sets the span status to
Errorwhenever the handler (or a downstream middleware) returns a non-nil error — even when that error resolves to a 4xx response, e.g.echo.NewHTTPError(http.StatusBadRequest, ...)or any error implementingecho.HTTPStatusCoder.Per the OpenTelemetry HTTP semantic conventions (HTTP spans / Status):
The middleware creates spans with
trace.SpanKindServer, so this rule applies to the spans it produces. Returning an*echo.HTTPErroris the idiomatic way for an Echo handler to produce a 4xx response, so in practice 400/404/422 responses produced this way are currently reported as errored spans, which makes it harder to distinguish server-side failures (5xx) from ordinary client errors (4xx) in monitoring backends.Root cause
In
otel.gothe resolved response status is passed toSpanStatus:echo-opentelemetry/otel.go
Lines 223 to 224 in 41c8bfd
echo.ResolveResponseStatuscorrectly resolvesHTTPError(400)(and anyHTTPStatusCodererror) tostatus = 400. However,SpanStatusreturns early whenerr != nil, so the resolved status code is not taken into account:echo-opentelemetry/extrator.go
Lines 574 to 577 in 41c8bfd
Note that the same resolved
statusis recorded as thehttp.response.status_codespan attribute, so the emitted span ends up a little inconsistent: the attribute reports400while the span status reportsError.Reproduction
Observed behavior on
main(41c8bfd):nilnilecho.NewHTTPError(400)HTTPStatusCodererror (422)echo.NewHTTPError(500)error(resolved to 500)Changes
SpanStatusnow bases the decision primarily on the resolved status code, and uses the error mainly for the status description:Error(description taken fromerrwhen present)Unset(server spans; the error is still returned to the caller and handled by Echo as before)err→Error("unless there was another error" clause of the spec, e.g. an error returned after a 2xx response was already committed)Error(unchanged)SpanStatus(4xx with*HTTPError/HTTPStatusCodererrors) and a middleware-level test asserting the span status for a handler returningecho.NewHTTPError(400).SpanStatusto quote theSpanKind.SERVER4xx rule.Existing tests are unaffected:
TestSpanStatus's "error overrides status code" case (200 + error →Error) still holds under the new logic, andTestSpanStatusOnHTTP500keeps assertingErrorfor 5xx.Related issues
err == nilpath (a 5xx status written directly by the handler is now reported asErrorbased on the resolved status code). This PR applies the same status-code-based decision to theerr != nilpath.otel.goalso sets theerror.typeattribute (semconv.ErrorType(err)) whenevererr != nil. Per the conventions this attribute is only expected when the request actually ended with an error (which for server spans excludes 4xx), and its value has its own requirements (e.g. the status code number as a string). Since http span and error.type #10 already trackserror.typehandling, I left that attribute untouched here to keep this PR focused on the span status — happy to help with http span and error.type #10 in a follow-up if useful.Please let me know if you'd like any changes to the approach or the tests — happy to update the PR.
🤖 Generated with Claude Code