module/apmgorilla/middleware.go (85 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package apmgorilla // import "go.elastic.co/apm/module/apmgorilla/v2"
import (
"net/http"
"github.com/gorilla/mux"
"go.elastic.co/apm/module/apmhttp/v2"
"go.elastic.co/apm/v2"
)
// Instrument instruments the mux.Router so that requests are traced.
//
// Instrument installs middleware into r, and alsos overrides
// r.NotFoundHandler and r.MethodNotAllowedHandler so that they
// are traced. If you modify either of those fields, you must do so
// before calling Instrument.
func Instrument(r *mux.Router, o ...Option) {
m := Middleware(o...)
r.Use(m)
r.NotFoundHandler = WrapNotFoundHandler(r.NotFoundHandler, m)
r.MethodNotAllowedHandler = WrapMethodNotAllowedHandler(r.MethodNotAllowedHandler, m)
}
// WrapNotFoundHandler wraps h with m. If h is nil, then http.NotFoundHandler() will be used.
func WrapNotFoundHandler(h http.Handler, m mux.MiddlewareFunc) http.Handler {
if h == nil {
h = http.NotFoundHandler()
}
return m(h)
}
// WrapMethodNotAllowedHandler wraps h with m. If h is nil, then a default handler
// will be used that returns status code 405.
func WrapMethodNotAllowedHandler(h http.Handler, m mux.MiddlewareFunc) http.Handler {
if h == nil {
h = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
})
}
return m(h)
}
// Middleware returns a new gorilla/mux middleware handler
// for tracing requests and reporting errors.
//
// This middleware will recover and report panics, so it can
// be used instead of the gorilla/middleware.RecoveryHandler
// middleware.
//
// Middleware does not get invoked when a route cannot be
// matched, or when an unsupported method is used. To report
// transactions in these cases, you should use the Instrument
// function, or set the router's NotFoundHandler and
// MethodNotAllowedHandler fields using the Wrap functions in
// this package.
//
// By default, the middleware will use apm.DefaultTracer().
// Use WithTracer to specify an alternative tracer.
func Middleware(o ...Option) mux.MiddlewareFunc {
opts := options{
tracer: apm.DefaultTracer(),
}
for _, o := range o {
o(&opts)
}
if opts.requestIgnorer == nil {
opts.requestIgnorer = apmhttp.NewDynamicServerRequestIgnorer(opts.tracer)
}
apmhttpOptions := []apmhttp.ServerOption{
apmhttp.WithTracer(opts.tracer),
apmhttp.WithServerRequestName(routeRequestName),
apmhttp.WithServerRequestIgnorer(opts.requestIgnorer),
}
if opts.panicPropagation {
apmhttpOptions = append(apmhttpOptions, apmhttp.WithPanicPropagation())
}
return func(h http.Handler) http.Handler {
return apmhttp.Wrap(h, apmhttpOptions...)
}
}
func routeRequestName(req *http.Request) string {
if route := mux.CurrentRoute(req); route != nil {
tpl, err := route.GetPathTemplate()
if err == nil {
return req.Method + " " + massageTemplate(tpl)
}
}
return apmhttp.UnknownRouteRequestName(req)
}
type options struct {
tracer *apm.Tracer
requestIgnorer apmhttp.RequestIgnorerFunc
panicPropagation bool
}
// Option sets options for tracing.
type Option func(*options)
// WithTracer returns an Option which sets t as the tracer
// to use for tracing server requests.
func WithTracer(t *apm.Tracer) Option {
if t == nil {
panic("t == nil")
}
return func(o *options) {
o.tracer = t
}
}
// WithRequestIgnorer returns a Option which sets r as the
// function to use to determine whether or not a request should
// be ignored. If r is nil, all requests will be reported.
func WithRequestIgnorer(r apmhttp.RequestIgnorerFunc) Option {
if r == nil {
r = apmhttp.IgnoreNone
}
return func(o *options) {
o.requestIgnorer = r
}
}
// WithPanicPropagation returns a Option which enable panic propagation.
// Any panic will be recovered and recorded as an error in a transaction, then
// panic will be caused again.
func WithPanicPropagation() Option {
return func(o *options) {
o.panicPropagation = true
}
}