diff --git a/CHANGELOG.md b/CHANGELOG.md index b15c3298b..0cf00f6a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Add trace origin to span data ([#849](https://github.com/getsentry/sentry-go/pull/849)) - Add ability to skip frames in stacktrace ([#852](https://github.com/getsentry/sentry-go/pull/852)) - Remove Martini integration ([#861](https://github.com/getsentry/sentry-go/pull/861)) +- Use value from http.Request.Pattern as HTTP server span name ([#875](https://github.com/getsentry/sentry-go/pull/875)) - Fix closure functions name grouping ([#877](https://github.com/getsentry/sentry-go/pull/877)) - Add net/http client integration ([#876](https://github.com/getsentry/sentry-go/pull/876)) diff --git a/http/sentryhttp.go b/http/sentryhttp.go index 9912530e5..f90bc2618 100644 --- a/http/sentryhttp.go +++ b/http/sentryhttp.go @@ -4,11 +4,11 @@ package sentryhttp import ( "context" - "fmt" "net/http" "time" "github.com/getsentry/sentry-go" + "github.com/getsentry/sentry-go/internal/traceutils" ) // The identifier of the HTTP SDK. @@ -102,7 +102,7 @@ func (h *Handler) handle(handler http.Handler) http.HandlerFunc { } transaction := sentry.StartTransaction(ctx, - fmt.Sprintf("%s %s", r.Method, r.URL.Path), + traceutils.GetHTTPSpanName(r), options..., ) transaction.SetData("http.request.method", r.Method) diff --git a/internal/traceutils/route_name_below_go1.23.go b/internal/traceutils/route_name_below_go1.23.go new file mode 100644 index 000000000..09f03aaa9 --- /dev/null +++ b/internal/traceutils/route_name_below_go1.23.go @@ -0,0 +1,10 @@ +//go:build !go1.23 + +package traceutils + +import "net/http" + +// GetHTTPSpanName grab needed fields from *http.Request to generate a span name for `http.server` span op. +func GetHTTPSpanName(r *http.Request) string { + return r.Method + " " + r.URL.Path +} diff --git a/internal/traceutils/route_name_below_go1.23_test.go b/internal/traceutils/route_name_below_go1.23_test.go new file mode 100644 index 000000000..ece97bce6 --- /dev/null +++ b/internal/traceutils/route_name_below_go1.23_test.go @@ -0,0 +1,30 @@ +//go:build !go1.23 + +package traceutils + +import ( + "net/http" + "net/url" + "testing" +) + +func TestGetHTTPSpanName(t *testing.T) { + tests := []struct { + name string + got string + want string + }{ + { + name: "Without Pattern", + got: GetHTTPSpanName(&http.Request{Method: "GET", URL: &url.URL{Path: "/"}}), + want: "GET /", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.want { + t.Errorf("got %q; want %q", tt.got, tt.want) + } + }) + } +} diff --git a/internal/traceutils/route_name_go1.23.go b/internal/traceutils/route_name_go1.23.go new file mode 100644 index 000000000..160675be9 --- /dev/null +++ b/internal/traceutils/route_name_go1.23.go @@ -0,0 +1,23 @@ +//go:build go1.23 + +package traceutils + +import ( + "net/http" + "strings" +) + +// GetHTTPSpanName grab needed fields from *http.Request to generate a span name for `http.server` span op. +func GetHTTPSpanName(r *http.Request) string { + if r.Pattern != "" { + // If value does not start with HTTP methods, add them. + // The method and the path should be separated by a space. + if parts := strings.SplitN(r.Pattern, " ", 2); len(parts) == 1 { + return r.Method + " " + r.Pattern + } + + return r.Pattern + } + + return r.Method + " " + r.URL.Path +} diff --git a/internal/traceutils/route_name_go1.23_test.go b/internal/traceutils/route_name_go1.23_test.go new file mode 100644 index 000000000..f7bb47cbf --- /dev/null +++ b/internal/traceutils/route_name_go1.23_test.go @@ -0,0 +1,45 @@ +//go:build go1.23 + +package traceutils + +import ( + "net/http" + "net/url" + "testing" +) + +func TestGetHTTPSpanName(t *testing.T) { + tests := []struct { + name string + got string + want string + }{ + { + name: "Without Pattern", + got: GetHTTPSpanName(&http.Request{Method: "GET", URL: &url.URL{Path: "/"}}), + want: "GET /", + }, + { + name: "Pattern with method", + got: GetHTTPSpanName(&http.Request{Method: "GET", URL: &url.URL{Path: "/"}, Pattern: "POST /foo/{bar}"}), + want: "POST /foo/{bar}", + }, + { + name: "Pattern without method", + got: GetHTTPSpanName(&http.Request{Method: "GET", URL: &url.URL{Path: "/"}, Pattern: "/foo/{bar}"}), + want: "GET /foo/{bar}", + }, + { + name: "Pattern without slash", + got: GetHTTPSpanName(&http.Request{Method: "GET", URL: &url.URL{Path: "/"}, Pattern: "example.com/"}), + want: "GET example.com/", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.got != tt.want { + t.Errorf("got %q; want %q", tt.got, tt.want) + } + }) + } +} diff --git a/negroni/sentrynegroni.go b/negroni/sentrynegroni.go index c7623e7dd..9e6ceda4a 100644 --- a/negroni/sentrynegroni.go +++ b/negroni/sentrynegroni.go @@ -2,11 +2,11 @@ package sentrynegroni import ( "context" - "fmt" "net/http" "time" "github.com/getsentry/sentry-go" + "github.com/getsentry/sentry-go/internal/traceutils" "github.com/urfave/negroni" ) @@ -71,7 +71,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha // We don't mind getting an existing transaction back so we don't need to // check if it is. transaction := sentry.StartTransaction(ctx, - fmt.Sprintf("%s %s", r.Method, r.URL.Path), + traceutils.GetHTTPSpanName(r), options..., ) transaction.SetData("http.request.method", r.Method)