generated from xmidt-org/.go-template
/
instrumenter.go
169 lines (143 loc) · 4.3 KB
/
instrumenter.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// SPDX-FileCopyrightText: 2022 Comcast Cable Communications Management, LLC
// SPDX-License-Identifier: Apache-2.0
package touchhttp
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/xmidt-org/httpaux"
"github.com/xmidt-org/httpaux/client"
"github.com/xmidt-org/httpaux/observe"
"github.com/xmidt-org/touchstone"
"go.uber.org/fx"
)
// transaction represents a completed HTTP transaction.
type transaction struct {
start time.Time
code int
method string
err error // that came from a client
requestSize int64
}
// instrumenter is the common logic that decorates HTTP transactions for
// both clients and servers.
type instrumenter struct {
count *prometheus.CounterVec
inFlight prometheus.Gauge
requestSize prometheus.ObserverVec
duration prometheus.ObserverVec
// only used in clients
errorCount *prometheus.CounterVec
now func() time.Time
}
// begin records the start of an HTTP transaction
func (i instrumenter) begin(r *http.Request) transaction {
i.inFlight.Inc()
return transaction{
start: i.now(),
method: r.Method,
requestSize: r.ContentLength,
}
}
func (i instrumenter) endHandle(sc observe.StatusCoder, t transaction) {
t.code = sc.StatusCode()
i.end(t)
}
func (i instrumenter) endDo(response *http.Response, err error, t transaction) {
if response != nil {
t.code = response.StatusCode
} else {
t.code = -1
}
t.err = err
i.end(t)
}
// end records the end of an HTTP transaction
func (i instrumenter) end(t transaction) {
i.inFlight.Dec()
l := prometheus.Labels(NewLabels(t.code, t.method))
i.count.With(l).Inc()
elapsed := i.now().Sub(t.start)
i.duration.With(l).Observe(
float64(elapsed / time.Millisecond),
)
i.requestSize.With(l).Observe(
float64(t.requestSize),
)
if i.errorCount != nil && t.err != nil {
i.errorCount.With(l).Inc()
}
}
// ServerInstrumenter is a serverside middleware that provides http.Handler
// metrics.
type ServerInstrumenter struct {
instrumenter
}
// Then is a server middleware that instruments the given handler. This middleware
// is compatible with justinas/alice and gorilla/mux.
func (si ServerInstrumenter) Then(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
w := observe.New(rw)
t := si.begin(r)
defer si.endHandle(w, t)
next.ServeHTTP(w, r)
})
}
// ClientInstrumenter is a clientside middleware that provides HTTP client
// metrics.
type ClientInstrumenter struct {
instrumenter
}
// Then is a client middleware that instruments the given client. This middleware
// is compatible with httpaux.
func (ci ClientInstrumenter) Then(next httpaux.Client) httpaux.Client {
return client.Func(func(request *http.Request) (response *http.Response, err error) {
t := ci.begin(request)
response, err = next.Do(request)
ci.endDo(response, err, t)
return
})
}
var _ client.Constructor = ClientInstrumenter{}.Then
// ServerInstrumenterIn defines the set of dependencies required to build a ServerInstrumenter.
type ServerInstrumenterIn struct {
fx.In
// Factory is the required touchstone Factory instance.
Factory *touchstone.Factory
// Bundle is the optional ServerBundle supplied in the application.
// If not present, the default metrics are used.
Bundle ServerBundle `optional:"true"`
}
// NewServerInstrumenter produces a constructor that can be passed to fx.Provide. The returned
// constructor allows a ServerBundle to be injected.
//
// Use this function when a ServerBundle has been supplied to the enclosing fx.App:
//
// app := fx.New(
// touchstone.Provide(), // bootstrap metrics subsystem
//
// fx.Provide(
// // A single, global ServerInstrumenter
// touchhttp.NewServerInstrumenter(),
//
// // A custom label
// touchhttp.NewServerInstrumenter(
// "custom1", "value",
// ),
//
// // A named ServerInstrumenter with a server label
// fx.Annotated{
// Name: "servers.main",
// Target: NewServerInstrumenter(
// touchhttp.ServerLabel, "servers.main",
// ),
// },
// ),
// )
func NewServerInstrumenter(namesAndValues ...string) func(ServerInstrumenterIn) (ServerInstrumenter, error) {
return func(in ServerInstrumenterIn) (ServerInstrumenter, error) {
return in.Bundle.NewInstrumenter(
namesAndValues...,
)(in.Factory)
}
}