File: nrgin.go

package info (click to toggle)
golang-github-newrelic-go-agent 3.15.2-9
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 8,356 kB
  • sloc: sh: 65; makefile: 6
file content (123 lines) | stat: -rw-r--r-- 3,461 bytes parent folder | download
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
// 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/_integrations/nrgin/v1/example/main.go
package nrgin

import (
	"net/http"

	"github.com/gin-gonic/gin"
	newrelic "github.com/newrelic/go-agent"
	"github.com/newrelic/go-agent/internal"
)

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
	txn     newrelic.Transaction
	code    int
	written bool
}

var _ gin.ResponseWriter = &replacementResponseWriter{}

func (w *replacementResponseWriter) flushHeader() {
	if !w.written {
		w.txn.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
}

// 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))
//
func Middleware(app newrelic.Application) gin.HandlerFunc {
	return func(c *gin.Context) {
		if app != nil {
			name := c.HandlerName()
			w := &headerResponseWriter{w: c.Writer}
			txn := app.StartTransaction(name, w, c.Request)
			defer txn.End()

			repl := &replacementResponseWriter{
				ResponseWriter: c.Writer,
				txn:            txn,
				code:           http.StatusOK,
			}
			c.Writer = repl
			defer repl.flushHeader()

			c.Set(internal.GinTransactionContextKey, txn)
		}
		c.Next()
	}
}