Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contrib: refactor http request span tags #1286

Merged
merged 25 commits into from May 31, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
41b9cbe
contrib: refactor http request span tags
Julio-Guerra May 11, 2022
9609d79
contrib/urfave/negroni: refactoring
Julio-Guerra May 11, 2022
eee06e3
contrib/emicklei/go-restful: refactoring
Julio-Guerra May 11, 2022
55cb685
contrib/urfave/negroni: fix error
Julio-Guerra May 11, 2022
9527174
contrib/gin-gonic: refactoring
Julio-Guerra May 11, 2022
d739ef8
contrib/emicklei/go-restful: refactoring
Julio-Guerra May 11, 2022
d05711e
contrib/go-chi/chi.v5: refactor
Julio-Guerra May 11, 2022
cca0d76
contrib/go-chi/chi: refactor
Julio-Guerra May 11, 2022
dd53641
contrib/labstack/echo: refactor
Julio-Guerra May 11, 2022
04cac98
contrib/labstack/echo.v4: refactor
Julio-Guerra May 11, 2022
029b8b5
contrib: create a new internal httptrace package
Julio-Guerra May 12, 2022
c2d56a8
contrib/net/http: restore nil config pointer condition
Julio-Guerra May 12, 2022
41f4c74
contrib/internal/httptrace: avoid converting the status code into a s…
Julio-Guerra May 12, 2022
8d9bd76
contrib/internal/httptrace: document how the request span is finished
Julio-Guerra May 12, 2022
7dff5a4
contrib/internal/httptrace: restore context change order
Julio-Guerra May 16, 2022
fcd3cd2
contrib/internal/httptrace: enabled measured traces by default
Julio-Guerra May 16, 2022
090cbed
contrib/net/htpp: normalize http.url tag
Hellzy May 12, 2022
e989bc6
contrib/internal/httptrace: list the currently supported standard htt…
Julio-Guerra May 23, 2022
36c85ad
contrib/internal/httptrace: fix missing argument
Julio-Guerra May 23, 2022
92607ef
contrib/internal/httptrace: documentation rework
Julio-Guerra May 23, 2022
95a7b98
contrib/internal/httptrace: change StartRequestSpan signature
Julio-Guerra May 23, 2022
c717b85
Fix lint
Julio-Guerra May 23, 2022
8c2a98a
contrib/internal/httptrace: integrate review comments
Julio-Guerra May 30, 2022
88b2925
Merge branch 'v1' into julio.guerra/backend-waf-tags
Julio-Guerra May 30, 2022
cdc8637
contrib/net/http: integrate review comments
Julio-Guerra May 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 7 additions & 17 deletions contrib/emicklei/go-restful/restful.go
Expand Up @@ -10,6 +10,7 @@ import (
"math"
"strconv"

httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
"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"
Expand All @@ -26,29 +27,18 @@ func FilterFunc(configOpts ...Option) restful.FilterFunction {
}
log.Debug("contrib/emicklei/go-restful: Creating tracing filter: %#v", cfg)
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),
}
var opt ddtrace.StartSpanOption
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))
opt = 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, cfg.serviceName, req.SelectedRoutePath(), false, opt)
defer func() {
httptrace.FinishRequestSpan(span, resp.StatusCode(), tracer.WithError(resp.Error()))
}()
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved

// 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())
}
}

Expand Down
25 changes: 6 additions & 19 deletions contrib/gin-gonic/gin/gintrace.go
Expand Up @@ -9,9 +9,8 @@ package gin // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/gin-gonic/gin"
import (
"fmt"
"math"
"net/http"
"strconv"

httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
"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"
Expand All @@ -34,27 +33,21 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc {
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(),
}
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, cfg.serviceName, cfg.resourceNamer(c), false, opts...)

// pass the span through the request context
c.Request = c.Request.WithContext(ctx)

defer func() {
httptrace.FinishRequestSpan(span, c.Writer.Status())
}()
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved

// Use AppSec if enabled by user
if appsecEnabled {
afterMiddleware := useAppSec(c, span)
Expand All @@ -64,12 +57,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())
}
Expand Down
61 changes: 37 additions & 24 deletions contrib/net/http/trace.go
Expand Up @@ -8,6 +8,7 @@ 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 (
"context"
"fmt"
"net/http"
"strconv"
Expand All @@ -26,7 +27,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
Expand All @@ -41,20 +42,33 @@ type ServeConfig struct {
// TraceAndServe serves the handler h using the given ResponseWriter and Request, applying tracing
// according to the specified config.
func TraceAndServe(h http.Handler, w http.ResponseWriter, r *http.Request, cfg *ServeConfig) {
if cfg == nil {
cfg = new(ServeConfig)
span, ctx := StartRequestSpan(r, cfg.Service, cfg.Resource, cfg.QueryParams, cfg.SpanOpts...)
rw, ddrw := wrapResponseWriter(w)
defer func() {
FinishRequestSpan(span, ddrw.status, cfg.FinishOpts...)
}()

if appsec.Enabled() {
h = httpsec.WrapHandler(h, span, cfg.RouteParams)
}
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved
h.ServeHTTP(rw, r.WithContext(ctx))
}

// StartRequestSpan starts an HTTP request span with the standard list of HTTP request span tags. URL query parameters
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved
// are added to the URL tag when queryParams is true. Any further span start option can be added with opts.
func StartRequestSpan(r *http.Request, service, resource string, queryParams bool, opts ...ddtrace.StartSpanOption) (tracer.Span, context.Context) {
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved
path := r.URL.Path
if cfg.QueryParams {
if queryParams {
path += "?" + r.URL.RawQuery
}
opts := append([]ddtrace.StartSpanOption{
opts = append([]ddtrace.StartSpanOption{
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved
tracer.SpanType(ext.SpanTypeWeb),
tracer.ServiceName(cfg.Service),
tracer.ResourceName(cfg.Resource),
tracer.ServiceName(service),
tracer.ResourceName(resource),
tracer.Tag(ext.HTTPMethod, r.Method),
tracer.Tag(ext.HTTPURL, path),
}, cfg.SpanOpts...)
tracer.Tag(ext.HTTPUserAgent, r.UserAgent()),
}, opts...)
if r.URL.Host != "" {
opts = append([]ddtrace.StartSpanOption{
tracer.Tag("http.host", r.URL.Host),
Expand All @@ -63,24 +77,23 @@ func TraceAndServe(h http.Handler, w http.ResponseWriter, r *http.Request, cfg *
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...)
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...)
}()
return tracer.StartSpanFromContext(r.Context(), "http.request", opts...)
}

if appsec.Enabled() {
h = httpsec.WrapHandler(h, span, cfg.RouteParams)
// FinishRequestSpan finishes the given HTTP request span with the standard list of HTTP request span tags.
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved
// 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)
}
h.ServeHTTP(rw, r.WithContext(ctx))
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved
s.SetTag(ext.HTTPCode, statusStr)
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved
if status >= 500 && status < 600 {
s.SetTag(ext.Error, fmt.Errorf("%d: %s", status, http.StatusText(status)))
}
s.Finish(opts...)
}

// responseWriter is a small wrapper around an http response writer that will
Expand Down
49 changes: 19 additions & 30 deletions contrib/urfave/negroni/negroni.go
Expand Up @@ -10,11 +10,10 @@ import (
"fmt"
"math"
"net/http"
"strconv"

"github.com/urfave/negroni"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
"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"
Expand All @@ -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.Measured())
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, m.cfg.serviceName, m.cfg.resourceNamer(r), false, 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
Expand Down
3 changes: 3 additions & 0 deletions ddtrace/ext/tags.go
Expand Up @@ -33,6 +33,9 @@ const (
// HTTPURL sets the HTTP URL for a span.
HTTPURL = "http.url"

// HTTPUserAgent sets the HTTP user agent tag.
Julio-Guerra marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down