diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b5c995c081..8f05a492daf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm For example, `[]string{"foo", "bar"}` attribute value is now transformed to `log.SliceValue(log.StringValue("foo"), log.StringValue("bar"))` instead of `log.String("[foo bar"])`. (#6254) - Add the `WithSource` option to the `go.opentelemetry.io/contrib/bridges/otelslog` log bridge to set the `code.*` attributes in the log record that includes the source location where the record was emitted. (#6253) - Add `ContextWithStartTime` and `StartTimeFromContext` to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`, which allows setting the start time using go context. (#6137) +- Add `routeName` argument to `SpanNameFormatter` function in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#5741) ### Fixed diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go index 1affd4d6ca5..1953e987431 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go @@ -75,7 +75,7 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { if cfg.SpanNameFormatter == nil { spanName = c.FullPath() } else { - spanName = cfg.SpanNameFormatter(c.Request) + spanName = cfg.SpanNameFormatter(c.FullPath(), c.Request) } if spanName == "" { spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method) diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/option.go b/instrumentation/github.com/gin-gonic/gin/otelgin/option.go index 143ca8e849e..5deb39611d7 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/option.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/option.go @@ -30,8 +30,8 @@ type Filter func(*http.Request) bool // gin.Context has FullPath() method, which returns a matched route full path. type GinFilter func(*gin.Context) bool -// SpanNameFormatter is used to set span name by http.request. -type SpanNameFormatter func(r *http.Request) string +// SpanNameFormatter is used to set span name by http.Request. +type SpanNameFormatter func(routeName string, r *http.Request) string // Option specifies instrumentation configuration options. type Option interface { @@ -84,9 +84,11 @@ func WithGinFilter(f ...GinFilter) Option { }) } -// WithSpanNameFormatter takes a function that will be called on every -// request and the returned string will become the Span Name. -func WithSpanNameFormatter(f func(r *http.Request) string) Option { +// WithSpanNameFormatter specifies a function to use for generating a custom span +// name. By default, the route name (path template or regexp) is used. The route +// name is provided so you can use it in the span name without needing to +// duplicate the logic for extracting it from the request. +func WithSpanNameFormatter(f func(routeName string, r *http.Request) string) Option { return optionFunc(func(c *config) { c.SpanNameFormatter = f }) diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go index fb8698422dc..12f8b279615 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go @@ -7,6 +7,7 @@ package test import ( "errors" + "fmt" "html/template" "net/http" "net/http/httptest" @@ -160,7 +161,8 @@ func TestSpanName(t *testing.T) { wantSpanName string }{ {"/user/1", nil, "/user/:id"}, - {"/user/1", func(r *http.Request) string { return r.URL.Path }, "/user/1"}, + {"/user/1", func(routeName string, r *http.Request) string { return r.URL.Path }, "/user/1"}, + {"/user/1", func(routeName string, r *http.Request) string { return fmt.Sprintf("%s %s", r.Method, routeName) }, "GET /user/:id"}, } for _, tc := range testCases { t.Run(tc.requestPath, func(t *testing.T) { @@ -171,7 +173,7 @@ func TestSpanName(t *testing.T) { router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider), otelgin.WithSpanNameFormatter(tc.spanNameFormatter))) router.GET("/user/:id", func(c *gin.Context) {}) - router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", tc.requestPath, nil)) + router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, tc.requestPath, nil)) require.Len(t, sr.Ended(), 1, "should emit a span") assert.Equal(t, tc.wantSpanName, sr.Ended()[0].Name(), "span name not correct") @@ -186,8 +188,8 @@ func TestHTTPRouteWithSpanNameFormatter(t *testing.T) { router := gin.New() router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider), - otelgin.WithSpanNameFormatter(func(r *http.Request) string { - return r.URL.Path + otelgin.WithSpanNameFormatter(func(routeName string, r *http.Request) string { + return fmt.Sprintf("%s %s", r.Method, routeName) }), ), ) @@ -208,7 +210,7 @@ func TestHTTPRouteWithSpanNameFormatter(t *testing.T) { spans := sr.Ended() require.Len(t, spans, 1) span := spans[0] - assert.Equal(t, "/user/123", span.Name()) + assert.Equal(t, "GET /user/:id", span.Name()) assert.Equal(t, oteltrace.SpanKindServer, span.SpanKind()) attr := span.Attributes() assert.Contains(t, attr, attribute.String("http.method", "GET"))