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 154 155 156 157 158 159 160 161 162 163
|
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
// Package nrgin instruments https://github.com/gin-gonic/gin applications.
//
// Use this package to instrument inbound requests handled by a gin.Engine.
// Call nrgin.Middleware to get a gin.HandlerFunc which can be added to your
// application as a middleware:
//
// router := gin.Default()
// // Add the nrgin middleware before other middlewares or routes:
// router.Use(nrgin.Middleware(app))
//
// Example: https://github.com/newrelic/go-agent/tree/master/v3/integrations/nrgin/example/main.go
package nrgin
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/newrelic/go-agent/v3/internal"
newrelic "github.com/newrelic/go-agent/v3/newrelic"
)
func init() { internal.TrackUsage("integration", "framework", "gin", "v1") }
// headerResponseWriter gives the transaction access to response headers and the
// response code.
type headerResponseWriter struct{ w gin.ResponseWriter }
func (w *headerResponseWriter) Header() http.Header { return w.w.Header() }
func (w *headerResponseWriter) Write([]byte) (int, error) { return 0, nil }
func (w *headerResponseWriter) WriteHeader(int) {}
var _ http.ResponseWriter = &headerResponseWriter{}
// replacementResponseWriter mimics the behavior of gin.ResponseWriter which
// buffers the response code rather than writing it when
// gin.ResponseWriter.WriteHeader is called.
type replacementResponseWriter struct {
gin.ResponseWriter
replacement http.ResponseWriter
code int
written bool
}
var _ gin.ResponseWriter = &replacementResponseWriter{}
func (w *replacementResponseWriter) flushHeader() {
if !w.written {
w.replacement.WriteHeader(w.code)
w.written = true
}
}
func (w *replacementResponseWriter) WriteHeader(code int) {
w.code = code
w.ResponseWriter.WriteHeader(code)
}
func (w *replacementResponseWriter) Write(data []byte) (int, error) {
w.flushHeader()
return w.ResponseWriter.Write(data)
}
func (w *replacementResponseWriter) WriteString(s string) (int, error) {
w.flushHeader()
return w.ResponseWriter.WriteString(s)
}
func (w *replacementResponseWriter) WriteHeaderNow() {
w.flushHeader()
w.ResponseWriter.WriteHeaderNow()
}
// Context avoids making this package 1.7+ specific.
type Context interface {
Value(key interface{}) interface{}
}
// Transaction returns the transaction stored inside the context, or nil if not
// found.
func Transaction(c Context) *newrelic.Transaction {
if v := c.Value(internal.GinTransactionContextKey); nil != v {
if txn, ok := v.(*newrelic.Transaction); ok {
return txn
}
}
if v := c.Value(internal.TransactionContextKey); nil != v {
if txn, ok := v.(*newrelic.Transaction); ok {
return txn
}
}
return nil
}
type handlerNamer interface {
HandlerName() string
}
func getName(c handlerNamer, useNewNames bool) string {
if useNewNames {
if fp, ok := c.(interface{ FullPath() string }); ok {
return fp.FullPath()
}
}
return c.HandlerName()
}
// Middleware creates a Gin middleware that instruments requests.
//
// router := gin.Default()
// // Add the nrgin middleware before other middlewares or routes:
// router.Use(nrgin.Middleware(app))
//
// Gin v1.5.0 introduced the gin.Context.FullPath method which allows for much
// improved transaction naming. This Middleware will use that
// gin.Context.FullPath if available and fall back to the original
// gin.Context.HandlerName if not. If you are using Gin v1.5.0 and wish to
// continue using the old transaction names, use
// nrgin.MiddlewareHandlerTxnNames.
func Middleware(app *newrelic.Application) gin.HandlerFunc {
return middleware(app, true)
}
// MiddlewareHandlerTxnNames creates a Gin middleware that instruments
// requests.
//
// router := gin.Default()
// // Add the nrgin middleware before other middlewares or routes:
// router.Use(nrgin.MiddlewareHandlerTxnNames(app))
//
// The use of gin.Context.HandlerName for naming transactions will be removed
// in a future release. Available in Gin v1.5.0 and newer is the
// gin.Context.FullPath method which allows for much improved transaction
// names. Use nrgin.Middleware to take full advantage of this new naming!
func MiddlewareHandlerTxnNames(app *newrelic.Application) gin.HandlerFunc {
return middleware(app, false)
}
func middleware(app *newrelic.Application, useNewNames bool) gin.HandlerFunc {
return func(c *gin.Context) {
if app != nil {
name := c.Request.Method + " " + getName(c, useNewNames)
w := &headerResponseWriter{w: c.Writer}
txn := app.StartTransaction(name)
txn.SetWebRequestHTTP(c.Request)
defer txn.End()
repl := &replacementResponseWriter{
ResponseWriter: c.Writer,
replacement: txn.SetWebResponse(w),
code: http.StatusOK,
}
c.Writer = repl
defer repl.flushHeader()
c.Set(internal.GinTransactionContextKey, txn)
}
c.Next()
}
}
|