/
nrecho.go
153 lines (125 loc) · 4.2 KB
/
nrecho.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// Package nrecho instruments applications using
// https://github.com/labstack/echo v4.
//
// Use this package to instrument inbound requests handled by an echo.Echo
// instance.
//
// e := echo.New()
// // Add the nrecho middleware before other middlewares or routes:
// e.Use(nrecho.Middleware(app))
//
// Example: https://github.com/newrelic/go-agent/tree/master/v3/integrations/nrecho-v4/example/main.go
package nrecho
import (
"net/http"
"reflect"
"github.com/labstack/echo/v4"
"github.com/newrelic/go-agent/v3/internal"
newrelic "github.com/newrelic/go-agent/v3/newrelic"
)
func init() { internal.TrackUsage("integration", "framework", "echo") }
// FromContext returns the Transaction from the context if present, and nil
// otherwise.
func FromContext(c echo.Context) *newrelic.Transaction {
return newrelic.FromContext(c.Request().Context())
}
func handlerPointer(handler echo.HandlerFunc) uintptr {
return reflect.ValueOf(handler).Pointer()
}
func transactionName(c echo.Context) string {
ptr := handlerPointer(c.Handler())
if ptr == handlerPointer(echo.NotFoundHandler) {
return "NotFoundHandler"
}
if ptr == handlerPointer(echo.MethodNotAllowedHandler) {
return "MethodNotAllowedHandler"
}
return c.Request().Method + " " + c.Path()
}
// Skipper defines a function to skip middleware. Returning true skips processing
// the middleware.
type Skipper func(c echo.Context) bool
// Config defines the config for the middleware.
type Config struct {
// App contains newrelic application.
App *newrelic.Application
// Skipper defines a function to skip middleware.
Skipper Skipper
}
type ConfigOption func(*Config)
func WithSkipper(skipper Skipper) ConfigOption {
return func(cfg *Config) { cfg.Skipper = skipper }
}
// Middleware creates Echo middleware with provided config that
// instruments requests.
//
// e := echo.New()
// // Add the nrecho middleware before other middlewares or routes:
// e.Use(nrecho.MiddlewareWithConfig(nrecho.Config{App: app}))
func Middleware(app *newrelic.Application, opts ...ConfigOption) func(echo.HandlerFunc) echo.HandlerFunc {
if app == nil {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return next
}
}
config := Config{
App: app,
}
for _, opt := range opts {
opt(&config)
}
if config.Skipper == nil {
// set default skipper
config.Skipper = func(echo.Context) bool {
return false
}
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
if config.Skipper(c) {
return next(c)
}
rw := c.Response().Writer
txn := config.App.StartTransaction(transactionName(c))
defer txn.End()
txn.SetWebRequestHTTP(c.Request())
c.Response().Writer = txn.SetWebResponse(rw)
// Add txn to c.Request().Context()
c.SetRequest(c.Request().WithContext(newrelic.NewContext(c.Request().Context(), txn)))
err = next(c)
// Record the response code. The response headers are not captured
// in this case because they are set after this middleware returns.
// Designed to mimic the logic in echo.DefaultHTTPErrorHandler.
if nil != err && !c.Response().Committed {
c.Response().Writer = rw
if httperr, ok := err.(*echo.HTTPError); ok {
txn.SetWebResponse(nil).WriteHeader(httperr.Code)
} else {
txn.SetWebResponse(nil).WriteHeader(http.StatusInternalServerError)
}
}
return
}
}
}
// WrapRouter extracts API endpoints from the echo instance passed to it
// which is used to detect application URL mapping(api-endpoints) for provable security.
// In this version of the integration, this wrapper is only necessary if you are using the New Relic security agent integration [https://github.com/newrelic/go-agent/tree/master/v3/integrations/nrsecurityagent],
// but it may be enhanced to provide additional functionality in future releases.
// e := echo.New()
// ....
// ....
// ....
//
// nrecho.WrapRouter(e)
//
func WrapRouter(engine *echo.Echo) {
if engine != nil && newrelic.IsSecurityAgentPresent() {
router := engine.Routes()
for _, r := range router {
newrelic.GetSecurityAgentInterface().SendEvent("API_END_POINTS", r.Path, r.Method, r.Name)
}
}
}