1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
// +build go1.7
package nethttp
import (
"net/http"
"net/url"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
)
type mwOptions struct {
opNameFunc func(r *http.Request) string
spanFilter func(r *http.Request) bool
spanObserver func(span opentracing.Span, r *http.Request)
urlTagFunc func(u *url.URL) string
componentName string
}
// MWOption controls the behavior of the Middleware.
type MWOption func(*mwOptions)
// OperationNameFunc returns a MWOption that uses given function f
// to generate operation name for each server-side span.
func OperationNameFunc(f func(r *http.Request) string) MWOption {
return func(options *mwOptions) {
options.opNameFunc = f
}
}
// MWComponentName returns a MWOption that sets the component name
// for the server-side span.
func MWComponentName(componentName string) MWOption {
return func(options *mwOptions) {
options.componentName = componentName
}
}
// MWSpanFilter returns a MWOption that filters requests from creating a span
// for the server-side span.
// Span won't be created if it returns false.
func MWSpanFilter(f func(r *http.Request) bool) MWOption {
return func(options *mwOptions) {
options.spanFilter = f
}
}
// MWSpanObserver returns a MWOption that observe the span
// for the server-side span.
func MWSpanObserver(f func(span opentracing.Span, r *http.Request)) MWOption {
return func(options *mwOptions) {
options.spanObserver = f
}
}
// MWURLTagFunc returns a MWOption that uses given function f
// to set the span's http.url tag. Can be used to change the default
// http.url tag, eg to redact sensitive information.
func MWURLTagFunc(f func(u *url.URL) string) MWOption {
return func(options *mwOptions) {
options.urlTagFunc = f
}
}
// Middleware wraps an http.Handler and traces incoming requests.
// Additionally, it adds the span to the request's context.
//
// By default, the operation name of the spans is set to "HTTP {method}".
// This can be overriden with options.
//
// Example:
// http.ListenAndServe("localhost:80", nethttp.Middleware(tracer, http.DefaultServeMux))
//
// The options allow fine tuning the behavior of the middleware.
//
// Example:
// mw := nethttp.Middleware(
// tracer,
// http.DefaultServeMux,
// nethttp.OperationNameFunc(func(r *http.Request) string {
// return "HTTP " + r.Method + ":/api/customers"
// }),
// nethttp.MWSpanObserver(func(sp opentracing.Span, r *http.Request) {
// sp.SetTag("http.uri", r.URL.EscapedPath())
// }),
// )
func Middleware(tr opentracing.Tracer, h http.Handler, options ...MWOption) http.Handler {
return MiddlewareFunc(tr, h.ServeHTTP, options...)
}
// MiddlewareFunc wraps an http.HandlerFunc and traces incoming requests.
// It behaves identically to the Middleware function above.
//
// Example:
// http.ListenAndServe("localhost:80", nethttp.MiddlewareFunc(tracer, MyHandler))
func MiddlewareFunc(tr opentracing.Tracer, h http.HandlerFunc, options ...MWOption) http.HandlerFunc {
opts := mwOptions{
opNameFunc: func(r *http.Request) string {
return "HTTP " + r.Method
},
spanFilter: func(r *http.Request) bool { return true },
spanObserver: func(span opentracing.Span, r *http.Request) {},
urlTagFunc: func(u *url.URL) string {
return u.String()
},
}
for _, opt := range options {
opt(&opts)
}
// set component name, use "net/http" if caller does not specify
componentName := opts.componentName
if componentName == "" {
componentName = defaultComponentName
}
fn := func(w http.ResponseWriter, r *http.Request) {
if !opts.spanFilter(r) {
h(w, r)
return
}
ctx, _ := tr.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
sp := tr.StartSpan(opts.opNameFunc(r), ext.RPCServerOption(ctx))
ext.HTTPMethod.Set(sp, r.Method)
ext.HTTPUrl.Set(sp, opts.urlTagFunc(r.URL))
ext.Component.Set(sp, componentName)
opts.spanObserver(sp, r)
sct := &statusCodeTracker{ResponseWriter: w}
r = r.WithContext(opentracing.ContextWithSpan(r.Context(), sp))
defer func() {
panicErr := recover()
didPanic := panicErr != nil
if sct.status == 0 && !didPanic {
// Standard behavior of http.Server is to assume status code 200 if one was not written by a handler that returned successfully.
// https://github.com/golang/go/blob/fca286bed3ed0e12336532cc711875ae5b3cb02a/src/net/http/server.go#L120
sct.status = 200
}
if sct.status > 0 {
ext.HTTPStatusCode.Set(sp, uint16(sct.status))
}
if sct.status >= http.StatusInternalServerError || didPanic {
ext.Error.Set(sp, true)
}
sp.Finish()
if didPanic {
panic(panicErr)
}
}()
h(sct.wrappedResponseWriter(), r)
}
return http.HandlerFunc(fn)
}
|