/
http.go
208 lines (180 loc) · 5.26 KB
/
http.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package metrics
import (
"errors"
"fmt"
"net/http"
"runtime"
"time"
"github.com/Jeffail/benthos/v3/internal/docs"
"github.com/Jeffail/benthos/v3/lib/log"
"github.com/Jeffail/gabs/v2"
)
func init() {
Constructors[TypeHTTPServer] = TypeSpec{
constructor: NewHTTP,
Summary: `
Serves metrics as [JSON object](#object-format) with the service wide HTTP
service at the endpoints ` + "`/stats` and `/metrics`" + `.`,
Description: `
This metrics type is useful for debugging as it provides a human readable format
that you can parse with tools such as ` + "`jq`" + ``,
Footnotes: `
## Object Format
The metrics object takes the form of a hierarchical representation of the dot
paths for each metric combined. So, for example, if Benthos exposed two metric
counters ` + "`foo.bar` and `bar.baz`" + ` then the resulting object might look
like this:
` + "``` json" + `
{
"foo": {
"bar": 9
},
"bar": {
"baz": 3
}
}
` + "```" + ``,
FieldSpecs: docs.FieldSpecs{
docs.FieldCommon("prefix", "A string prefix to add to all metrics."),
pathMappingDocs(false, false),
},
}
}
//------------------------------------------------------------------------------
// Errors for the HTTP type.
var (
ErrTimedOut = errors.New("timed out")
)
//------------------------------------------------------------------------------
// HTTPConfig contains configuration parameters for the HTTP metrics aggregator.
type HTTPConfig struct {
Prefix string `json:"prefix" yaml:"prefix"`
PathMapping string `json:"path_mapping" yaml:"path_mapping"`
}
// NewHTTPConfig returns a new HTTPConfig with default values.
func NewHTTPConfig() HTTPConfig {
return HTTPConfig{
Prefix: "benthos",
PathMapping: "",
}
}
//------------------------------------------------------------------------------
// HTTP is an object with capability to hold internal stats as a JSON endpoint.
type HTTP struct {
local *Local
log log.Modular
timestamp time.Time
pathPrefix string
pathMapping *pathMapping
}
// NewHTTP creates and returns a new HTTP object.
func NewHTTP(config Config, opts ...func(Type)) (Type, error) {
t := &HTTP{
local: NewLocal(),
timestamp: time.Now(),
pathPrefix: config.HTTP.Prefix,
}
for _, opt := range opts {
opt(t)
}
var err error
if t.pathMapping, err = newPathMapping(config.HTTP.PathMapping, t.log); err != nil {
return nil, fmt.Errorf("failed to init path mapping: %v", err)
}
return t, nil
}
//------------------------------------------------------------------------------
func (h *HTTP) getPath(path string) string {
path = h.pathMapping.mapPathNoTags(path)
if len(h.pathPrefix) > 0 && len(path) > 0 {
path = h.pathPrefix + "." + path
}
return path
}
// HandlerFunc returns an http.HandlerFunc for accessing metrics as a JSON blob.
func (h *HTTP) HandlerFunc() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uptime := time.Since(h.timestamp).String()
goroutines := runtime.NumGoroutine()
counters := h.local.GetCounters()
timings := h.local.GetTimings()
obj := gabs.New()
for k, v := range counters {
obj.SetP(v, k)
}
for k, v := range timings {
obj.SetP(v, k)
obj.SetP(time.Duration(v).String(), k+"_readable")
}
obj.SetP(uptime, "uptime")
obj.SetP(goroutines, "goroutines")
w.Header().Set("Content-Type", "application/json")
w.Write(obj.Bytes())
}
}
// GetCounter returns a stat counter object for a path.
func (h *HTTP) GetCounter(path string) StatCounter {
if path = h.getPath(path); path == "" {
return DudStat{}
}
return h.local.GetCounter(path)
}
// GetCounterVec returns a stat counter object for a path with the labels
// discarded.
func (h *HTTP) GetCounterVec(path string, n []string) StatCounterVec {
if path = h.getPath(path); path == "" {
return fakeCounterVec(func([]string) StatCounter {
return DudStat{}
})
}
return fakeCounterVec(func([]string) StatCounter {
return h.local.GetCounter(path)
})
}
// GetTimer returns a stat timer object for a path.
func (h *HTTP) GetTimer(path string) StatTimer {
if path = h.getPath(path); path == "" {
return DudStat{}
}
return h.local.GetTimer(path)
}
// GetTimerVec returns a stat timer object for a path with the labels
// discarded.
func (h *HTTP) GetTimerVec(path string, n []string) StatTimerVec {
if path = h.getPath(path); path == "" {
return fakeTimerVec(func([]string) StatTimer {
return DudStat{}
})
}
return fakeTimerVec(func([]string) StatTimer {
return h.local.GetTimer(path)
})
}
// GetGauge returns a stat gauge object for a path.
func (h *HTTP) GetGauge(path string) StatGauge {
if path = h.getPath(path); path == "" {
return DudStat{}
}
return h.local.GetGauge(path)
}
// GetGaugeVec returns a stat timer object for a path with the labels
// discarded.
func (h *HTTP) GetGaugeVec(path string, n []string) StatGaugeVec {
if path = h.getPath(path); path == "" {
return fakeGaugeVec(func([]string) StatGauge {
return DudStat{}
})
}
return fakeGaugeVec(func([]string) StatGauge {
return h.local.GetGauge(path)
})
}
// SetLogger does nothing.
func (h *HTTP) SetLogger(log log.Modular) {
h.log = log
}
// Close stops the HTTP object from aggregating metrics and cleans up resources.
func (h *HTTP) Close() error {
return nil
}
//------------------------------------------------------------------------------