module/apmgin/middleware.go (109 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 apmgin // import "go.elastic.co/apm/module/apmgin/v2"
import (
"net/http"
"github.com/gin-gonic/gin"
"go.elastic.co/apm/module/apmhttp/v2"
"go.elastic.co/apm/v2"
"go.elastic.co/apm/v2/stacktrace"
)
func init() {
stacktrace.RegisterLibraryPackage(
"github.com/gin-gonic",
"github.com/gin-contrib",
)
}
// Middleware returns a new Gin middleware handler for tracing
// requests and reporting errors.
//
// This middleware will recover and report panics, so it can
// be used instead of the standard gin.Recovery middleware.
//
// By default, the middleware will use apm.DefaultTracer().
// Use WithTracer to specify an alternative tracer.
func Middleware(engine *gin.Engine, o ...Option) gin.HandlerFunc {
m := &middleware{
engine: engine,
tracer: apm.DefaultTracer(),
panicPropagation: false,
}
for _, o := range o {
o(m)
}
if m.requestIgnorer == nil {
m.requestIgnorer = apmhttp.NewDynamicServerRequestIgnorer(m.tracer)
}
return m.handle
}
type middleware struct {
engine *gin.Engine
tracer *apm.Tracer
requestIgnorer apmhttp.RequestIgnorerFunc
panicPropagation bool
}
func (m *middleware) handle(c *gin.Context) {
if !m.tracer.Recording() || m.requestIgnorer(c.Request) {
c.Next()
return
}
requestName := m.getRequestName(c)
tx, body, req := apmhttp.StartTransactionWithBody(m.tracer, requestName, c.Request)
defer tx.End()
c.Request = req
defer func() {
if v := recover(); v != nil {
if m.panicPropagation {
defer panic(v)
} else {
if !c.Writer.Written() {
c.AbortWithStatus(http.StatusInternalServerError)
} else {
c.Abort()
}
}
e := m.tracer.Recovered(v)
e.SetTransaction(tx)
setContext(&e.Context, c, body)
e.Send()
}
tx.Result = apmhttp.StatusCodeResult(c.Writer.Status())
if tx.Sampled() {
setContext(&tx.Context, c, body)
}
for _, err := range c.Errors {
e := m.tracer.NewError(err.Err)
e.SetTransaction(tx)
setContext(&e.Context, c, body)
e.Handled = true
e.Send()
}
body.Discard()
}()
c.Next()
}
func (m *middleware) getRequestName(c *gin.Context) string {
if fullPath := c.FullPath(); fullPath != "" {
return c.Request.Method + " " + fullPath
}
return apmhttp.UnknownRouteRequestName(c.Request)
}
func setContext(ctx *apm.Context, c *gin.Context, body *apm.BodyCapturer) {
ctx.SetFramework("gin", gin.Version)
ctx.SetHTTPRequest(c.Request)
ctx.SetHTTPRequestBody(body)
ctx.SetHTTPStatusCode(c.Writer.Status())
ctx.SetHTTPResponseHeaders(c.Writer.Header())
}
// Option sets options for tracing.
type Option func(*middleware)
// 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(m *middleware) {
m.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(m *middleware) {
m.requestIgnorer = r
}
}
// WithPanicPropagation returns an 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(m *middleware) {
m.panicPropagation = true
}
}