This repository has been archived by the owner on Nov 8, 2019. It is now read-only.
forked from elastic/apm-agent-go
/
middleware.go
131 lines (115 loc) · 3.19 KB
/
middleware.go
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
package apmgin
import (
"net/http"
"sync"
"github.com/gin-gonic/gin"
"github.com/elastic/apm-agent-go"
"github.com/elastic/apm-agent-go/module/apmhttp"
"github.com/elastic/apm-agent-go/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 elasticapm.DefaultTracer.
// Use WithTracer to specify an alternative tracer.
func Middleware(engine *gin.Engine, o ...Option) gin.HandlerFunc {
m := &middleware{engine: engine, tracer: elasticapm.DefaultTracer}
for _, o := range o {
o(m)
}
return m.handle
}
type middleware struct {
engine *gin.Engine
tracer *elasticapm.Tracer
setRouteMapOnce sync.Once
routeMap map[string]map[string]routeInfo
}
type routeInfo struct {
transactionName string // e.g. "GET /foo"
}
func (m *middleware) handle(c *gin.Context) {
if !m.tracer.Active() {
c.Next()
return
}
m.setRouteMapOnce.Do(func() {
routes := m.engine.Routes()
rm := make(map[string]map[string]routeInfo)
for _, r := range routes {
mm := rm[r.Method]
if mm == nil {
mm = make(map[string]routeInfo)
rm[r.Method] = mm
}
mm[r.Handler] = routeInfo{
transactionName: r.Method + " " + r.Path,
}
}
m.routeMap = rm
})
requestName := c.Request.Method
handlerName := c.HandlerName()
if routeInfo, ok := m.routeMap[c.Request.Method][handlerName]; ok {
requestName = routeInfo.transactionName
}
tx := m.tracer.StartTransaction(requestName, "request")
ctx := elasticapm.ContextWithTransaction(c.Request.Context(), tx)
c.Request = apmhttp.RequestWithContext(ctx, c.Request)
defer tx.End()
body := m.tracer.CaptureHTTPRequestBody(c.Request)
ginContext := ginContext{Handler: handlerName}
defer func() {
if v := recover(); v != nil {
c.AbortWithStatus(http.StatusInternalServerError)
e := m.tracer.Recovered(v, tx)
e.Context.SetHTTPRequest(c.Request)
e.Context.SetHTTPRequestBody(body)
e.Send()
}
tx.Result = apmhttp.StatusCodeResult(c.Writer.Status())
if tx.Sampled() {
tx.Context.SetHTTPRequest(c.Request)
tx.Context.SetHTTPRequestBody(body)
tx.Context.SetHTTPStatusCode(c.Writer.Status())
tx.Context.SetHTTPResponseHeaders(c.Writer.Header())
tx.Context.SetHTTPResponseHeadersSent(c.Writer.Written())
tx.Context.SetHTTPResponseFinished(!c.IsAborted())
tx.Context.SetCustom("gin", ginContext)
}
for _, err := range c.Errors {
e := m.tracer.NewError(err.Err)
e.Context.SetHTTPRequest(c.Request)
e.Context.SetHTTPRequestBody(body)
e.Context.SetCustom("gin", ginContext)
e.Transaction = tx
e.Handled = true
e.Send()
}
}()
c.Next()
}
type ginContext struct {
Handler string `json:"handler"`
}
// 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 *elasticapm.Tracer) Option {
if t == nil {
panic("t == nil")
}
return func(m *middleware) {
m.tracer = t
}
}