diff --git a/contrib/gin-gonic/gin/gintrace.go b/contrib/gin-gonic/gin/gintrace.go index f4b4a3a500..33dc894d82 100644 --- a/contrib/gin-gonic/gin/gintrace.go +++ b/contrib/gin-gonic/gin/gintrace.go @@ -39,6 +39,7 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { if !math.IsNaN(cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) } + opts = append(opts, tracer.Tag(ext.HTTPRoute, c.FullPath())) span, ctx := httptrace.StartRequestSpan(c.Request, opts...) defer func() { httptrace.FinishRequestSpan(span, c.Writer.Status()) diff --git a/contrib/go-chi/chi.v5/chi.go b/contrib/go-chi/chi.v5/chi.go index 2bb2dc83db..2c9aedd565 100644 --- a/contrib/go-chi/chi.v5/chi.go +++ b/contrib/go-chi/chi.v5/chi.go @@ -66,6 +66,7 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { // set the resource name as we get it only once the handler is executed resourceName := chi.RouteContext(r.Context()).RoutePattern() + span.SetTag(ext.HTTPRoute, resourceName) if resourceName == "" { resourceName = "unknown" } diff --git a/contrib/go-chi/chi/chi.go b/contrib/go-chi/chi/chi.go index f4832f6f95..5acfb09c7e 100644 --- a/contrib/go-chi/chi/chi.go +++ b/contrib/go-chi/chi/chi.go @@ -66,6 +66,7 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { // set the resource name as we get it only once the handler is executed resourceName := chi.RouteContext(r.Context()).RoutePattern() + span.SetTag(ext.HTTPRoute, resourceName) if resourceName == "" { resourceName = "unknown" } diff --git a/contrib/gorilla/mux/mux.go b/contrib/gorilla/mux/mux.go index 2207230f10..c1653dc7c0 100644 --- a/contrib/gorilla/mux/mux.go +++ b/contrib/gorilla/mux/mux.go @@ -89,12 +89,14 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { var ( match mux.RouteMatch spanopts []ddtrace.StartSpanOption + route string ) // get the resource associated to this request if r.Match(req, &match) && match.Route != nil { if h, err := match.Route.GetHostTemplate(); err == nil { spanopts = append(spanopts, tracer.Tag("mux.host", h)) } + route, _ = match.Route.GetPathTemplate() } spanopts = append(spanopts, r.config.spanOpts...) if r.config.headerTags { @@ -108,6 +110,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { SpanOpts: spanopts, QueryParams: r.config.queryParams, RouteParams: match.Vars, + Route: route, }) } diff --git a/contrib/labstack/echo.v4/echotrace.go b/contrib/labstack/echo.v4/echotrace.go index e6ae78c8d5..8c85143a1d 100644 --- a/contrib/labstack/echo.v4/echotrace.go +++ b/contrib/labstack/echo.v4/echotrace.go @@ -34,8 +34,9 @@ func Middleware(opts ...Option) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { request := c.Request() - resource := request.Method + " " + c.Path() - opts := append(spanOpts, tracer.ResourceName(resource)) + route := c.Path() + resource := request.Method + " " + route + opts := append(spanOpts, tracer.ResourceName(resource), tracer.Tag(ext.HTTPRoute, route)) if !math.IsNaN(cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) diff --git a/contrib/net/http/http.go b/contrib/net/http/http.go index 47c2790845..2382866ab8 100644 --- a/contrib/net/http/http.go +++ b/contrib/net/http/http.go @@ -49,6 +49,7 @@ func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { Service: mux.cfg.serviceName, Resource: resource, SpanOpts: mux.cfg.spanOpts, + Route: route, }) } @@ -74,6 +75,7 @@ func WrapHandler(h http.Handler, service, resource string, opts ...Option) http. Resource: resource, FinishOpts: cfg.finishOpts, SpanOpts: cfg.spanOpts, + Route: req.URL.EscapedPath(), }) }) } diff --git a/contrib/net/http/trace.go b/contrib/net/http/trace.go index 92c3095114..2c7fa1a77a 100644 --- a/contrib/net/http/trace.go +++ b/contrib/net/http/trace.go @@ -27,6 +27,8 @@ type ServeConfig struct { Resource string // QueryParams should be true in order to append the URL query values to the "http.url" tag. QueryParams bool + // Route is the request matched route if any, or is empty otherwise + Route string // RouteParams specifies framework-specific route parameters (e.g. for route /user/:id coming // in as /user/123 we'll have {"id": "123"}). This field is optional and is used for monitoring // by AppSec. It is only taken into account when AppSec is enabled. @@ -47,6 +49,7 @@ func TraceAndServe(h http.Handler, w http.ResponseWriter, r *http.Request, cfg * if cfg.QueryParams { opts = append(opts, tracer.Tag(ext.HTTPURL, r.URL.Path+"?"+r.URL.RawQuery)) } + opts = append(opts, tracer.Tag(ext.HTTPRoute, cfg.Route)) span, ctx := httptrace.StartRequestSpan(r, opts...) rw, ddrw := wrapResponseWriter(w) defer func() { diff --git a/ddtrace/ext/tags.go b/ddtrace/ext/tags.go index 767bd4ea7c..b76024f54a 100644 --- a/ddtrace/ext/tags.go +++ b/ddtrace/ext/tags.go @@ -30,6 +30,9 @@ const ( // HTTPCode sets the HTTP status code as a tag. HTTPCode = "http.status_code" + // HTTPRoute is the route value of the HTTP request. + HTTPRoute = "http.route" + // HTTPURL sets the HTTP URL for a span. HTTPURL = "http.url"