Skip to content

Instantly share code, notes, and snippets.

@pierrre
Created February 1, 2018 15:01
Show Gist options
  • Save pierrre/d6f86351ce384a30b548c8ef2ecb6ebf to your computer and use it in GitHub Desktop.
Save pierrre/d6f86351ce384a30b548c8ef2ecb6ebf to your computer and use it in GitHub Desktop.
DataDog HTTP tracing
// Package mux provides tracing functions for tracing the gorilla/mux package (https://github.com/gorilla/mux).
package mux
import (
"net/http"
tracing_net_http "github.com/xxx/yyy/tracing/net/http"
"github.com/gorilla/mux"
)
// Router registers routes to be matched and dispatches a handler.
type Router struct {
*mux.Router
service string
}
// NewRouterWithServiceName returns a new router instance which traces under the given service
// name.
//
// TODO(gbbr): Remove tracer parameter once we switch to OpenTracing.
func NewRouterWithServiceName(service string) *Router {
return &Router{
Router: mux.NewRouter(),
service: service,
}
}
// ServeHTTP dispatches the request to the handler
// whose pattern most closely matches the request URL.
// We only need to rewrite this function to be able to trace
// all the incoming requests to the underlying multiplexer
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var (
match mux.RouteMatch
route string
err error
)
// get the resource associated to this request
if r.Match(req, &match) {
route, err = match.Route.GetPathTemplate()
if err != nil {
route = "unknown"
}
} else {
route = "unknown"
}
resource := req.Method + " " + route
tracing_net_http.TraceAndServe(r.Router, w, req, r.service, resource)
}
// Package http provides functions to trace the net/http package (https://golang.org/pkg/net/http).
package http
import (
"net/http"
)
// ServeMux is an HTTP request multiplexer that traces all the incoming requests.
type ServeMux struct {
*http.ServeMux
service string
}
// NewServeMuxWithServiceName creates a new http.ServeMux that is traced using
// the given service name.
//
// TODO(gbbr): Remove this once we switch to OpenTracing.
func NewServeMuxWithServiceName(service string) *ServeMux {
return &ServeMux{
ServeMux: http.NewServeMux(),
service: service,
}
}
// ServeHTTP dispatches the request to the handler
// whose pattern most closely matches the request URL.
// We only need to rewrite this function to be able to trace
// all the incoming requests to the underlying multiplexer
func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// get the resource associated to this request
_, route := mux.Handler(r)
resource := r.Method + " " + route
TraceAndServe(mux.ServeMux, w, r, mux.service, resource)
}
// WrapHandler wraps an http.Handler with the default tracer using the
// specified service and resource.
func WrapHandler(h http.Handler, service, resource string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
TraceAndServe(h, w, req, service, resource)
})
}
package http
import (
"net/http"
"strconv"
ddtrace_opentracing "github.com/DataDog/dd-trace-go/opentracing"
ddtrace_tracer_ext "github.com/DataDog/dd-trace-go/tracer/ext"
"github.com/opentracing/opentracing-go"
)
// TraceAndServe will apply tracing to the given http.Handler using the passed tracer under the given service and resource.
func TraceAndServe(h http.Handler, w http.ResponseWriter, r *http.Request, service, resource string) {
span, ctx := opentracing.StartSpanFromContext(r.Context(), "http.request")
defer span.Finish()
span.SetTag(ddtrace_opentracing.SpanType, ddtrace_tracer_ext.HTTPType)
span.SetTag(ddtrace_opentracing.ServiceName, service)
span.SetTag(ddtrace_opentracing.ResourceName, resource)
span.SetTag(ddtrace_tracer_ext.HTTPMethod, r.Method)
span.SetTag(ddtrace_tracer_ext.HTTPURL, r.URL.Path)
traceRequest := r.WithContext(ctx)
traceWriter := NewResponseWriter(w, span)
h.ServeHTTP(traceWriter, traceRequest)
}
// ResponseWriter is a small wrapper around an http response writer that will
// intercept and store the status of a request.
// It implements the ResponseWriter interface.
type ResponseWriter struct {
http.ResponseWriter
span opentracing.Span
status int
}
// NewResponseWriter allocateds and returns a new ResponseWriter.
func NewResponseWriter(w http.ResponseWriter, span opentracing.Span) *ResponseWriter {
return &ResponseWriter{w, span, 0}
}
// Write writes the data to the connection as part of an HTTP reply.
// We explicitly call WriteHeader with the 200 status code
// in order to get it reported into the span.
func (w *ResponseWriter) Write(b []byte) (int, error) {
if w.status == 0 {
w.WriteHeader(http.StatusOK)
}
return w.ResponseWriter.Write(b)
}
// WriteHeader sends an HTTP response header with status code.
// It also sets the status code to the span.
func (w *ResponseWriter) WriteHeader(status int) {
w.ResponseWriter.WriteHeader(status)
w.status = status
w.span.SetTag(ddtrace_tracer_ext.HTTPCode, strconv.Itoa(status))
if status >= 500 && status < 600 {
if span, ok := w.span.(*ddtrace_opentracing.Span); ok {
span.Error = 1
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment