Skip to content

Commit

Permalink
contrib/net/http: Support go1.22 ServeMux patterns (#2716)
Browse files Browse the repository at this point in the history
  • Loading branch information
felixge authored May 30, 2024
1 parent 4e98120 commit dd54cad
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 1 deletion.
17 changes: 16 additions & 1 deletion contrib/net/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package http // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"

import (
"net/http"
"strings"

"gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
Expand Down Expand Up @@ -49,7 +50,8 @@ func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// get the resource associated to this request
_, route := mux.Handler(r)
_, pattern := mux.Handler(r)
route := patternRoute(pattern)
resource := mux.cfg.resourceNamer(r)
if resource == "" {
resource = r.Method + " " + route
Expand All @@ -65,6 +67,19 @@ func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
})
}

// patternRoute returns the route part of a go1.22 style ServeMux pattern. I.e.
// it returns "/foo" for the pattern "/foo" as well as the pattern "GET /foo".
func patternRoute(s string) string {
// Support go1.22 serve mux patterns: [METHOD ][HOST]/[PATH]
// Consider any text before a space or tab to be the method of the pattern.
// See net/http.parsePattern and the link below for more information.
// https://pkg.go.dev/net/http#hdr-Patterns
if i := strings.IndexAny(s, " \t"); i > 0 && len(s) >= i+1 {
return strings.TrimLeft(s[i+1:], " \t")
}
return s
}

// WrapHandler wraps an http.Handler with tracing using the given service and resource.
// If the WithResourceNamer option is provided as part of opts, it will take precedence over the resource argument.
func WrapHandler(h http.Handler, service, resource string, opts ...Option) http.Handler {
Expand Down
46 changes: 46 additions & 0 deletions contrib/net/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,52 @@ func TestServeMuxUsesResourceNamer(t *testing.T) {
assert.Equal("net/http", s.Tag(ext.Component))
}

func TestServeMuxGo122Patterns(t *testing.T) {
mt := mocktracer.Start()
defer mt.Stop()

// A mux with go1.21 patterns ("/bar") and go1.22 patterns ("GET /foo")
mux := NewServeMux()
mux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {})
mux.HandleFunc("GET /foo", func(w http.ResponseWriter, r *http.Request) {})

// Try to hit both routes
barW := httptest.NewRecorder()
mux.ServeHTTP(barW, httptest.NewRequest("GET", "/bar", nil))
fooW := httptest.NewRecorder()
mux.ServeHTTP(fooW, httptest.NewRequest("GET", "/foo", nil))

// Assert the number of spans
assert := assert.New(t)
spans := mt.FinishedSpans()
assert.Equal(2, len(spans))

// Check the /bar span
barSpan := spans[0]
assert.Equal(http.StatusOK, barW.Code)
assert.Equal("/bar", barSpan.Tag(ext.HTTPRoute))
assert.Equal("GET /bar", barSpan.Tag(ext.ResourceName))

// Check the /foo span
fooSpan := spans[1]
if fooW.Code == http.StatusOK {
assert.Equal("/foo", fooSpan.Tag(ext.HTTPRoute))
assert.Equal("GET /foo", fooSpan.Tag(ext.ResourceName))
} else {
// Until our go.mod version is go1.22 or greater, the mux will not
// understand the "GET /foo" pattern, causing the request to be handled
// by the 404 handler. Let's assert what we can, and mark the test as
// skipped to highlight the issue.
assert.Equal(http.StatusNotFound, fooW.Code)
assert.Equal(nil, fooSpan.Tag(ext.HTTPRoute))
// Using "GET " as a resource name doesn't seem ideal, but that's how
// the mux instrumentation deals with 404s right now.
assert.Equal("GET ", fooSpan.Tag(ext.ResourceName))
t.Skip("run `go mod edit -go=1.22` to run the full test")
}

}

func TestWrapHandlerWithResourceNameNoRace(_ *testing.T) {
mt := mocktracer.Start()
defer mt.Stop()
Expand Down

0 comments on commit dd54cad

Please sign in to comment.