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