Skip to content

fix(http): stop using raw URL path as http.route for net/http - #761

Open
happym51888 wants to merge 1 commit into
alibaba:mainfrom
happym51888:fix/http-route-semconv-nethttp
Open

happym51888 wants to merge 1 commit into
alibaba:mainfrom
happym51888:fix/http-route-semconv-nethttp

Conversation

@happym51888

Copy link
Copy Markdown
Contributor

Summary

  • net/http server instrumentation now sets http.route from Request.Pattern only (Go 1.22+ ServeMux patterns).
  • Omit http.route when no template exists instead of falling back to url.path.
  • Span-name fallback now only promotes obvious route templates ({id}, *, :param), not raw paths like /users/123.

Problem

High-cardinality http.route labels on REST APIs when raw paths were used (semconv violation). See #729.

Test plan

  • Added http_route_test.go unit tests.
  • Updated nethttp/zap/logrus/zerolog integration expectations for legacy HandleFunc servers.

Fixes #729 (net/http scope; fasthttp/fiber follow-ups can be separate).

Made with Cursor

Use Request.Pattern when available, omit http.route otherwise, and only promote span-name routes that look like templates. Fixes alibaba#729 (net/http portion).

Co-authored-by: Cursor <cursoragent@cursor.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Happy Mu seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@happym51888

Copy link
Copy Markdown
Contributor Author

Note: this overlaps partially with open #733 (broader framework route template work). Happy to narrow or close this PR if maintainers prefer to land #733 first; net/http Pattern + span-name template heuristic was scoped as a smaller slice of #729.

@y1yang0 y1yang0 left a comment

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.

Thanks for tackling #729. Moving away from the raw URL path as http.route is the right call. However, the PR can't be merged yet: the net/http rule doesn't compile, and even once it does, the route is never picked up.

Blocking

1. pkg/rules/http fails to compile

netHttpRouteFromPattern is unexported in pkg/inst-api-semconv/instrumenter/http, but it's called from a different package (and module), pkg/rules/http:

pkg/rules/http/net_http_otel_instrumenter.go:213:9: undefined: netHttpRouteFromPattern

Every otel go build of an app using net/http would break. Please either export it (e.g. http.NetHttpRouteFromPattern) or move it into pkg/rules/http.

2. r.Pattern is read too early, so it's always empty

The server hook is on serverHandler.ServeHTTP, and serverOnEnter reads r.Pattern at entry. But ServeMux.ServeHTTP only sets it later:

h, r.Pattern, r.pat, r.matches = mux.findHandler(r)

As a result, routePattern is always "". The span name becomes plain GET, and http.route is always omitted, even for requests that matched a ServeMux pattern.

Suggested fix:

  • Keep the *http.Request in call.SetData(...) and read r.Pattern in serverOnExit. ServeMux mutates the same request, so the value is there by then.
  • The span name is decided at Start, so rename the span once the route is known, e.g. span.SetName(method + " " + route).
  • Known limitation, worth a code comment: if middleware in front of the mux calls r.WithContext(...), the pattern is set on the copy and the outer hook still won't see it.

3. The integration test expectations can't pass

The updated tests in test/nethttp, test/zap, test/logrus and test/zerolog expect the span name "GET /a" and http.route to be omitted at the same time. Neither version of the code satisfies both:

  • With the current PR, the span name is "GET" (see #2), so span.Name == name fails.
  • With #2 fixed, http.route would be "/a", so the "must be omitted" assertion fails.

Only the old behaviour (raw path as route) produced "GET /a", which suggests these tests weren't run against this change. Expected values should be:

  • Handlers registered on a ServeMux (e.g. http.HandleFunc("/a", ...)): span name GET /a, http.route = /a.
  • A new case where no pattern matches (e.g. a custom http.Handler passed to http.Server): span name GET, http.route omitted.

Should fix

4. Host-prefixed patterns aren't normalized

netHttpRouteFromPattern returns "" for valid patterns such as example.com/users/{id}: they neither start with / nor contain a space. Strip the optional METHOD prefix first, then take everything from the first /:

if _, rest, ok := strings.Cut(pattern, " "); ok {
    pattern = rest
}
if i := strings.IndexByte(pattern, '/'); i >= 0 {
    return pattern[i:]
}
return ""

Please also add unit tests for example.com/x, GET example.com/x/{id}, /files/{path...} and /{$}.

5. httpRouteFromSpanName can bring back high cardinality

Any span name containing : or * is treated as a route template, so a raw path like /time/12:30 would be promoted to http.route. This runs in the shared HttpServerAttrsExtractor.OnEnd, so it affects every HTTP framework, not just net/http. Consider matching only real template syntax: {...}, or path segments that start with : or *, instead of a substring anywhere in the path.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Server http.route must not fall back to raw url.path (cardinality / semconv)

3 participants