diff --git a/contrib/emicklei/go-restful/restful.go b/contrib/emicklei/go-restful/restful.go index 5e76a06307..c09ed863c2 100644 --- a/contrib/emicklei/go-restful/restful.go +++ b/contrib/emicklei/go-restful/restful.go @@ -8,8 +8,8 @@ package restful import ( "math" - "strconv" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -25,52 +25,31 @@ func FilterFunc(configOpts ...Option) restful.FilterFunction { opt(cfg) } log.Debug("contrib/emicklei/go-restful: Creating tracing filter: %#v", cfg) + spanOpts := []ddtrace.StartSpanOption{tracer.ServiceName(cfg.serviceName)} return func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { - opts := []ddtrace.StartSpanOption{ - tracer.ServiceName(cfg.serviceName), - tracer.ResourceName(req.SelectedRoutePath()), - tracer.SpanType(ext.SpanTypeWeb), - tracer.Tag(ext.HTTPMethod, req.Request.Method), - tracer.Tag(ext.HTTPURL, req.Request.URL.Path), - } + spanOpts := append(spanOpts, tracer.ResourceName(req.SelectedRoutePath())) if !math.IsNaN(cfg.analyticsRate) { - opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) - } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(req.Request.Header)); err == nil { - opts = append(opts, tracer.ChildOf(spanctx)) + spanOpts = append(spanOpts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) } - span, ctx := tracer.StartSpanFromContext(req.Request.Context(), "http.request", opts...) - defer span.Finish() + span, ctx := httptrace.StartRequestSpan(req.Request, spanOpts...) + defer func() { + httptrace.FinishRequestSpan(span, resp.StatusCode(), tracer.WithError(resp.Error())) + }() // pass the span through the request context req.Request = req.Request.WithContext(ctx) - chain.ProcessFilter(req, resp) - - span.SetTag(ext.HTTPCode, strconv.Itoa(resp.StatusCode())) - span.SetTag(ext.Error, resp.Error()) } } // Filter is deprecated. Please use FilterFunc. func Filter(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { - opts := []ddtrace.StartSpanOption{ - tracer.ResourceName(req.SelectedRoutePath()), - tracer.SpanType(ext.SpanTypeWeb), - tracer.Tag(ext.HTTPMethod, req.Request.Method), - tracer.Tag(ext.HTTPURL, req.Request.URL.Path), - } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(req.Request.Header)); err == nil { - opts = append(opts, tracer.ChildOf(spanctx)) - } - span, ctx := tracer.StartSpanFromContext(req.Request.Context(), "http.request", opts...) - defer span.Finish() + span, ctx := httptrace.StartRequestSpan(req.Request, tracer.ResourceName(req.SelectedRoutePath())) + defer func() { + httptrace.FinishRequestSpan(span, resp.StatusCode(), tracer.WithError(resp.Error())) + }() // pass the span through the request context req.Request = req.Request.WithContext(ctx) - chain.ProcessFilter(req, resp) - - span.SetTag(ext.HTTPCode, strconv.Itoa(resp.StatusCode())) - span.SetTag(ext.Error, resp.Error()) } diff --git a/contrib/gin-gonic/gin/gintrace.go b/contrib/gin-gonic/gin/gintrace.go index a5b53d32e9..f4b4a3a500 100644 --- a/contrib/gin-gonic/gin/gintrace.go +++ b/contrib/gin-gonic/gin/gintrace.go @@ -9,10 +9,8 @@ package gin // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/gin-gonic/gin" import ( "fmt" "math" - "net/http" - "strconv" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" @@ -29,28 +27,22 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { for _, opt := range opts { opt(cfg) } - log.Debug("contrib/gin-gonic/gin: Configuring Middleware: Service: %s, %#v", service, cfg) + log.Debug("contrib/gin-gonic/gin: Configuring Middleware: Service: %s, %#v", cfg.serviceName, cfg) + spanOpts := []tracer.StartSpanOption{ + tracer.ServiceName(cfg.serviceName), + } return func(c *gin.Context) { if cfg.ignoreRequest(c) { return } - resource := cfg.resourceNamer(c) - opts := []ddtrace.StartSpanOption{ - tracer.ServiceName(cfg.serviceName), - tracer.ResourceName(resource), - tracer.SpanType(ext.SpanTypeWeb), - tracer.Tag(ext.HTTPMethod, c.Request.Method), - tracer.Tag(ext.HTTPURL, c.Request.URL.Path), - tracer.Measured(), - } + opts := append(spanOpts, tracer.ResourceName(cfg.resourceNamer(c))) if !math.IsNaN(cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(c.Request.Header)); err == nil { - opts = append(opts, tracer.ChildOf(spanctx)) - } - span, ctx := tracer.StartSpanFromContext(c.Request.Context(), "http.request", opts...) - defer span.Finish() + span, ctx := httptrace.StartRequestSpan(c.Request, opts...) + defer func() { + httptrace.FinishRequestSpan(span, c.Writer.Status()) + }() // pass the span through the request context c.Request = c.Request.WithContext(ctx) @@ -64,12 +56,6 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { // serve the request to the next middleware c.Next() - status := c.Writer.Status() - span.SetTag(ext.HTTPCode, strconv.Itoa(status)) - if status >= 500 && status < 600 { - span.SetTag(ext.Error, fmt.Errorf("%d: %s", status, http.StatusText(status))) - } - if len(c.Errors) > 0 { span.SetTag("gin.errors", c.Errors.String()) } diff --git a/contrib/go-chi/chi.v5/chi.go b/contrib/go-chi/chi.v5/chi.go index 9ca29e24f9..2bb2dc83db 100644 --- a/contrib/go-chi/chi.v5/chi.go +++ b/contrib/go-chi/chi.v5/chi.go @@ -10,9 +10,8 @@ import ( "fmt" "math" "net/http" - "strconv" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" @@ -30,28 +29,30 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { fn(cfg) } log.Debug("contrib/go-chi/chi.v5: Configuring Middleware: %#v", cfg) + spanOpts := append(cfg.spanOpts, tracer.ServiceName(cfg.serviceName)) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if cfg.ignoreRequest(r) { next.ServeHTTP(w, r) return } - opts := []ddtrace.StartSpanOption{ - tracer.SpanType(ext.SpanTypeWeb), - tracer.ServiceName(cfg.serviceName), - tracer.Tag(ext.HTTPMethod, r.Method), - tracer.Tag(ext.HTTPURL, r.URL.Path), - tracer.Measured(), - } + opts := spanOpts if !math.IsNaN(cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil { - opts = append(opts, tracer.ChildOf(spanctx)) - } - opts = append(opts, cfg.spanOpts...) - span, ctx := tracer.StartSpanFromContext(r.Context(), "http.request", opts...) - defer span.Finish() + span, ctx := httptrace.StartRequestSpan(r, opts...) + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + defer func() { + status := ww.Status() + var opts []tracer.FinishOption + if cfg.isStatusError(status) { + opts = []tracer.FinishOption{tracer.WithError(fmt.Errorf("%d: %s", status, http.StatusText(status)))} + } + httptrace.FinishRequestSpan(span, status, opts...) + }() + + // pass the span through the request context + r = r.WithContext(ctx) next := next // avoid modifying the value of next in the outer closure scope if appsec.Enabled() { @@ -60,10 +61,8 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { // implements the `interface { Status() int }` expected by httpsec. } - ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) - // pass the span through the request context and serve the request to the next middleware - next.ServeHTTP(ww, r.WithContext(ctx)) + next.ServeHTTP(ww, r) // set the resource name as we get it only once the handler is executed resourceName := chi.RouteContext(r.Context()).RoutePattern() @@ -72,19 +71,6 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { } resourceName = r.Method + " " + resourceName span.SetTag(ext.ResourceName, resourceName) - - // set the status code - status := ww.Status() - // 0 status means one has not yet been sent in which case net/http library will write StatusOK - if ww.Status() == 0 { - status = http.StatusOK - } - span.SetTag(ext.HTTPCode, strconv.Itoa(status)) - - if cfg.isStatusError(status) { - // mark 5xx server error - span.SetTag(ext.Error, fmt.Errorf("%d: %s", status, http.StatusText(status))) - } }) } } diff --git a/contrib/go-chi/chi/chi.go b/contrib/go-chi/chi/chi.go index 08c37dbabc..f4832f6f95 100644 --- a/contrib/go-chi/chi/chi.go +++ b/contrib/go-chi/chi/chi.go @@ -10,9 +10,8 @@ import ( "fmt" "math" "net/http" - "strconv" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" @@ -30,28 +29,30 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { fn(cfg) } log.Debug("contrib/go-chi/chi: Configuring Middleware: %#v", cfg) + spanOpts := append(cfg.spanOpts, tracer.ServiceName(cfg.serviceName)) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if cfg.ignoreRequest(r) { next.ServeHTTP(w, r) return } - opts := []ddtrace.StartSpanOption{ - tracer.SpanType(ext.SpanTypeWeb), - tracer.ServiceName(cfg.serviceName), - tracer.Tag(ext.HTTPMethod, r.Method), - tracer.Tag(ext.HTTPURL, r.URL.Path), - tracer.Measured(), - } + opts := spanOpts if !math.IsNaN(cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil { - opts = append(opts, tracer.ChildOf(spanctx)) - } - opts = append(opts, cfg.spanOpts...) - span, ctx := tracer.StartSpanFromContext(r.Context(), "http.request", opts...) - defer span.Finish() + span, ctx := httptrace.StartRequestSpan(r, opts...) + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + defer func() { + status := ww.Status() + var opts []tracer.FinishOption + if cfg.isStatusError(status) { + opts = []tracer.FinishOption{tracer.WithError(fmt.Errorf("%d: %s", status, http.StatusText(status)))} + } + httptrace.FinishRequestSpan(span, status, opts...) + }() + + // pass the span through the request context + r = r.WithContext(ctx) next := next // avoid modifying the value of next in the outer closure scope if appsec.Enabled() { @@ -60,10 +61,8 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { // implements the `interface { Status() int }` expected by httpsec. } - ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) - // pass the span through the request context and serve the request to the next middleware - next.ServeHTTP(ww, r.WithContext(ctx)) + next.ServeHTTP(ww, r) // set the resource name as we get it only once the handler is executed resourceName := chi.RouteContext(r.Context()).RoutePattern() @@ -72,19 +71,6 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler { } resourceName = r.Method + " " + resourceName span.SetTag(ext.ResourceName, resourceName) - - // set the status code - status := ww.Status() - // 0 status means one has not yet been sent in which case net/http library will write StatusOK - if ww.Status() == 0 { - status = http.StatusOK - } - span.SetTag(ext.HTTPCode, strconv.Itoa(status)) - - if cfg.isStatusError(status) { - // mark 5xx server error - span.SetTag(ext.Error, fmt.Errorf("%d: %s", status, http.StatusText(status))) - } }) } } diff --git a/contrib/gorilla/mux/option.go b/contrib/gorilla/mux/option.go index 2d960244c5..7690b392c5 100644 --- a/contrib/gorilla/mux/option.go +++ b/contrib/gorilla/mux/option.go @@ -39,7 +39,6 @@ func newConfig(opts []RouterOption) *routerConfig { if !math.IsNaN(cfg.analyticsRate) { cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) } - cfg.spanOpts = append(cfg.spanOpts, tracer.Measured()) return cfg } diff --git a/contrib/internal/httptrace/httptrace.go b/contrib/internal/httptrace/httptrace.go new file mode 100644 index 0000000000..87aa14cf0d --- /dev/null +++ b/contrib/internal/httptrace/httptrace.go @@ -0,0 +1,57 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016 Datadog, Inc. + +// Package httptrace provides functionalities to trace HTTP requests that are commonly required and used across +// contrib/** integrations. +package httptrace + +import ( + "context" + "fmt" + "net/http" + "strconv" + + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +// StartRequestSpan starts an HTTP request span with the standard list of HTTP request span tags (http.method, http.url, +// http.useragent). Any further span start option can be added with opts. +func StartRequestSpan(r *http.Request, opts ...ddtrace.StartSpanOption) (tracer.Span, context.Context) { + // Append our span options before the given ones so that the caller can "overwrite" them. + opts = append([]ddtrace.StartSpanOption{ + tracer.SpanType(ext.SpanTypeWeb), + tracer.Tag(ext.HTTPMethod, r.Method), + tracer.Tag(ext.HTTPURL, r.URL.Path), + tracer.Tag(ext.HTTPUserAgent, r.UserAgent()), + tracer.Measured(), + }, opts...) + if r.URL.Host != "" { + opts = append([]ddtrace.StartSpanOption{ + tracer.Tag("http.host", r.URL.Host), + }, opts...) + } + if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil { + opts = append(opts, tracer.ChildOf(spanctx)) + } + return tracer.StartSpanFromContext(r.Context(), "http.request", opts...) +} + +// FinishRequestSpan finishes the given HTTP request span and sets the expected response-related tags such as the status +// code. Any further span finish option can be added with opts. +func FinishRequestSpan(s tracer.Span, status int, opts ...tracer.FinishOption) { + var statusStr string + if status == 0 { + statusStr = "200" + } else { + statusStr = strconv.Itoa(status) + } + s.SetTag(ext.HTTPCode, statusStr) + if status >= 500 && status < 600 { + s.SetTag(ext.Error, fmt.Errorf("%s: %s", statusStr, http.StatusText(status))) + } + s.Finish(opts...) +} diff --git a/contrib/julienschmidt/httprouter/httprouter.go b/contrib/julienschmidt/httprouter/httprouter.go index 4889d53791..e4b5e493cc 100644 --- a/contrib/julienschmidt/httprouter/httprouter.go +++ b/contrib/julienschmidt/httprouter/httprouter.go @@ -35,7 +35,6 @@ func New(opts ...RouterOption) *Router { if !math.IsNaN(cfg.analyticsRate) { cfg.spanOpts = append(cfg.spanOpts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) } - cfg.spanOpts = append(cfg.spanOpts, tracer.Measured()) log.Debug("contrib/julienschmidt/httprouter: Configuring Router: %#v", cfg) return &Router{httprouter.New(), cfg} } diff --git a/contrib/labstack/echo.v4/echotrace.go b/contrib/labstack/echo.v4/echotrace.go index fc16489626..e6ae78c8d5 100644 --- a/contrib/labstack/echo.v4/echotrace.go +++ b/contrib/labstack/echo.v4/echotrace.go @@ -8,12 +8,13 @@ package echo import ( "math" - "strconv" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "github.com/labstack/echo/v4" ) @@ -26,31 +27,29 @@ func Middleware(opts ...Option) echo.MiddlewareFunc { for _, fn := range opts { fn(cfg) } + log.Debug("contrib/labstack/echo.v4: Configuring Middleware: %#v", cfg) + spanOpts := []ddtrace.StartSpanOption{ + tracer.ServiceName(cfg.serviceName), + } return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { request := c.Request() resource := request.Method + " " + c.Path() - opts := []ddtrace.StartSpanOption{ - tracer.ServiceName(cfg.serviceName), - tracer.ResourceName(resource), - tracer.SpanType(ext.SpanTypeWeb), - tracer.Tag(ext.HTTPMethod, request.Method), - tracer.Tag(ext.HTTPURL, request.URL.Path), - tracer.Measured(), - } + opts := append(spanOpts, tracer.ResourceName(resource)) if !math.IsNaN(cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(request.Header)); err == nil { - opts = append(opts, tracer.ChildOf(spanctx)) - } + var finishOpts []tracer.FinishOption if cfg.noDebugStack { - finishOpts = append(finishOpts, tracer.NoDebugStack()) + finishOpts = []tracer.FinishOption{tracer.NoDebugStack()} } - span, ctx := tracer.StartSpanFromContext(request.Context(), "http.request", opts...) - defer func() { span.Finish(finishOpts...) }() + + span, ctx := httptrace.StartRequestSpan(request, opts...) + defer func() { + httptrace.FinishRequestSpan(span, c.Response().Status, finishOpts...) + }() // pass the span through the request context c.SetRequest(request.WithContext(ctx)) @@ -66,7 +65,6 @@ func Middleware(opts ...Option) echo.MiddlewareFunc { c.Error(err) } - span.SetTag(ext.HTTPCode, strconv.Itoa(c.Response().Status)) return err } } diff --git a/contrib/labstack/echo/echotrace.go b/contrib/labstack/echo/echotrace.go index bd8aca362e..3d56d7ccc3 100644 --- a/contrib/labstack/echo/echotrace.go +++ b/contrib/labstack/echo/echotrace.go @@ -8,8 +8,8 @@ package echo import ( "math" - "strconv" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -20,37 +20,34 @@ import ( // Middleware returns echo middleware which will trace incoming requests. func Middleware(opts ...Option) echo.MiddlewareFunc { + cfg := new(config) + defaults(cfg) + for _, fn := range opts { + fn(cfg) + } + log.Debug("contrib/labstack/echo: Configuring Middleware: %#v", cfg) + spanOpts := []ddtrace.StartSpanOption{ + tracer.ServiceName(cfg.serviceName), + } return func(next echo.HandlerFunc) echo.HandlerFunc { - cfg := new(config) - defaults(cfg) - for _, fn := range opts { - fn(cfg) - } - log.Debug("contrib/labstack/echo: Configuring Middleware: %#v", cfg) return func(c echo.Context) error { request := c.Request() resource := request.Method + " " + c.Path() - opts := []ddtrace.StartSpanOption{ - tracer.ServiceName(cfg.serviceName), - tracer.ResourceName(resource), - tracer.SpanType(ext.SpanTypeWeb), - tracer.Tag(ext.HTTPMethod, request.Method), - tracer.Tag(ext.HTTPURL, request.URL.Path), - tracer.Measured(), - } + opts := append(spanOpts, tracer.ResourceName(resource)) if !math.IsNaN(cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, cfg.analyticsRate)) } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(request.Header)); err == nil { - opts = append(opts, tracer.ChildOf(spanctx)) - } + var finishOpts []tracer.FinishOption if cfg.noDebugStack { - finishOpts = append(finishOpts, tracer.NoDebugStack()) + finishOpts = []tracer.FinishOption{tracer.NoDebugStack()} } - span, ctx := tracer.StartSpanFromContext(request.Context(), "http.request", opts...) - defer func() { span.Finish(finishOpts...) }() + + span, ctx := httptrace.StartRequestSpan(request, opts...) + defer func() { + httptrace.FinishRequestSpan(span, c.Response().Status, finishOpts...) + }() // pass the span through the request context c.SetRequest(request.WithContext(ctx)) @@ -63,7 +60,6 @@ func Middleware(opts ...Option) echo.MiddlewareFunc { c.Error(err) } - span.SetTag(ext.HTTPCode, strconv.Itoa(c.Response().Status)) return err } } diff --git a/contrib/net/http/trace.go b/contrib/net/http/trace.go index 74d3413c09..92c3095114 100644 --- a/contrib/net/http/trace.go +++ b/contrib/net/http/trace.go @@ -8,10 +8,9 @@ package http // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" //go:generate sh -c "go run make_responsewriter.go | gofmt > trace_gen.go" import ( - "fmt" "net/http" - "strconv" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" @@ -26,7 +25,7 @@ type ServeConfig struct { Service string // Resource optionally specifies the resource name for this request. Resource string - // QueryParams specifies any query parameters that be appended to the resulting "http.url" tag. + // QueryParams should be true in order to append the URL query values to the "http.url" tag. QueryParams bool // 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 @@ -44,37 +43,14 @@ func TraceAndServe(h http.Handler, w http.ResponseWriter, r *http.Request, cfg * if cfg == nil { cfg = new(ServeConfig) } - path := r.URL.Path + opts := append(cfg.SpanOpts, tracer.ServiceName(cfg.Service), tracer.ResourceName(cfg.Resource)) if cfg.QueryParams { - path += "?" + r.URL.RawQuery + opts = append(opts, tracer.Tag(ext.HTTPURL, r.URL.Path+"?"+r.URL.RawQuery)) } - opts := append([]ddtrace.StartSpanOption{ - tracer.SpanType(ext.SpanTypeWeb), - tracer.ServiceName(cfg.Service), - tracer.ResourceName(cfg.Resource), - tracer.Tag(ext.HTTPMethod, r.Method), - tracer.Tag(ext.HTTPURL, path), - }, cfg.SpanOpts...) - if r.URL.Host != "" { - opts = append([]ddtrace.StartSpanOption{ - tracer.Tag("http.host", r.URL.Host), - }, opts...) - } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil { - opts = append(opts, tracer.ChildOf(spanctx)) - } - span, ctx := tracer.StartSpanFromContext(r.Context(), "http.request", opts...) + span, ctx := httptrace.StartRequestSpan(r, opts...) rw, ddrw := wrapResponseWriter(w) defer func() { - if ddrw.status == 0 { - span.SetTag(ext.HTTPCode, "200") - } else { - span.SetTag(ext.HTTPCode, strconv.Itoa(ddrw.status)) - } - if ddrw.status >= 500 && ddrw.status < 600 { - span.SetTag(ext.Error, fmt.Errorf("%d: %s", ddrw.status, http.StatusText(ddrw.status))) - } - span.Finish(cfg.FinishOpts...) + httptrace.FinishRequestSpan(span, ddrw.status, cfg.FinishOpts...) }() if appsec.Enabled() { diff --git a/contrib/urfave/negroni/negroni.go b/contrib/urfave/negroni/negroni.go index 8fd1d2a697..05168c28b6 100644 --- a/contrib/urfave/negroni/negroni.go +++ b/contrib/urfave/negroni/negroni.go @@ -10,11 +10,10 @@ import ( "fmt" "math" "net/http" - "strconv" "github.com/urfave/negroni" - "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" + "gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/httptrace" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" @@ -26,38 +25,28 @@ type DatadogMiddleware struct { } func (m *DatadogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - opts := []ddtrace.StartSpanOption{ - tracer.SpanType(ext.SpanTypeWeb), - tracer.ServiceName(m.cfg.serviceName), - tracer.Tag(ext.HTTPMethod, r.Method), - tracer.Tag(ext.HTTPURL, r.URL.Path), - tracer.Tag(ext.ResourceName, m.cfg.resourceNamer(r)), - tracer.Measured(), - } + opts := append(m.cfg.spanOpts, tracer.ServiceName(m.cfg.serviceName), tracer.ResourceName(m.cfg.resourceNamer(r))) if !math.IsNaN(m.cfg.analyticsRate) { opts = append(opts, tracer.Tag(ext.EventSampleRate, m.cfg.analyticsRate)) } - if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil { - opts = append(opts, tracer.ChildOf(spanctx)) - } - opts = append(opts, m.cfg.spanOpts...) - span, ctx := tracer.StartSpanFromContext(r.Context(), "http.request", opts...) - defer span.Finish() - - r = r.WithContext(ctx) - - next(w, r) - - // check if the responseWriter is of type negroni.ResponseWriter - responseWriter, ok := w.(negroni.ResponseWriter) - if ok { - status := responseWriter.Status() - span.SetTag(ext.HTTPCode, strconv.Itoa(status)) - if m.cfg.isStatusError(status) { - // mark 5xx server error - span.SetTag(ext.Error, fmt.Errorf("%d: %s", status, http.StatusText(status))) + span, ctx := httptrace.StartRequestSpan(r, opts...) + defer func() { + // check if the responseWriter is of type negroni.ResponseWriter + var ( + status int + opts []tracer.FinishOption + ) + responseWriter, ok := w.(negroni.ResponseWriter) + if ok { + status = responseWriter.Status() + if m.cfg.isStatusError(status) { + opts = []tracer.FinishOption{tracer.WithError(fmt.Errorf("%d: %s", status, http.StatusText(status)))} + } } - } + httptrace.FinishRequestSpan(span, status, opts...) + }() + + next(w, r.WithContext(ctx)) } // Middleware create the negroni middleware that will trace incoming requests diff --git a/ddtrace/ext/app_types.go b/ddtrace/ext/app_types.go index 03152fb787..63382cb85e 100644 --- a/ddtrace/ext/app_types.go +++ b/ddtrace/ext/app_types.go @@ -30,7 +30,7 @@ const ( // Span types have similar behaviour to "app types" and help categorize // traces in the Datadog application. They can also help fine grain agent -// level bahviours such as obfuscation and quantization, when these are +// level behaviours such as obfuscation and quantization, when these are // enabled in the agent's configuration. const ( // SpanTypeWeb marks a span as an HTTP server request. diff --git a/ddtrace/ext/tags.go b/ddtrace/ext/tags.go index 51d1cd3645..e7337d7ae7 100644 --- a/ddtrace/ext/tags.go +++ b/ddtrace/ext/tags.go @@ -33,6 +33,9 @@ const ( // HTTPURL sets the HTTP URL for a span. HTTPURL = "http.url" + // HTTPUserAgent is the user agent header value of the HTTP request. + HTTPUserAgent = "http.useragent" + // SpanName is a pseudo-key for setting a span's operation name by means of // a tag. It is mostly here to facilitate vendor-agnostic frameworks like Opentracing // and OpenCensus.