github.com/Jeffail/benthos/v3@v3.65.0/lib/metrics/http.go (about) 1 package metrics 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 "runtime" 8 "time" 9 10 "github.com/Jeffail/benthos/v3/internal/docs" 11 "github.com/Jeffail/benthos/v3/lib/log" 12 "github.com/Jeffail/gabs/v2" 13 ) 14 15 func init() { 16 Constructors[TypeHTTPServer] = TypeSpec{ 17 constructor: NewHTTP, 18 Summary: ` 19 Serves metrics as [JSON object](#object-format) with the service wide HTTP 20 service at the endpoints ` + "`/stats` and `/metrics`" + `.`, 21 Description: ` 22 This metrics type is useful for debugging as it provides a human readable format 23 that you can parse with tools such as ` + "`jq`" + ``, 24 Footnotes: ` 25 ## Object Format 26 27 The metrics object takes the form of a hierarchical representation of the dot 28 paths for each metric combined. So, for example, if Benthos exposed two metric 29 counters ` + "`foo.bar` and `bar.baz`" + ` then the resulting object might look 30 like this: 31 32 ` + "``` json" + ` 33 { 34 "foo": { 35 "bar": 9 36 }, 37 "bar": { 38 "baz": 3 39 } 40 } 41 ` + "```" + ``, 42 FieldSpecs: docs.FieldSpecs{ 43 docs.FieldCommon("prefix", "A string prefix to add to all metrics."), 44 pathMappingDocs(false, false), 45 }, 46 } 47 } 48 49 //------------------------------------------------------------------------------ 50 51 // Errors for the HTTP type. 52 var ( 53 ErrTimedOut = errors.New("timed out") 54 ) 55 56 //------------------------------------------------------------------------------ 57 58 // HTTPConfig contains configuration parameters for the HTTP metrics aggregator. 59 type HTTPConfig struct { 60 Prefix string `json:"prefix" yaml:"prefix"` 61 PathMapping string `json:"path_mapping" yaml:"path_mapping"` 62 } 63 64 // NewHTTPConfig returns a new HTTPConfig with default values. 65 func NewHTTPConfig() HTTPConfig { 66 return HTTPConfig{ 67 Prefix: "benthos", 68 PathMapping: "", 69 } 70 } 71 72 //------------------------------------------------------------------------------ 73 74 // HTTP is an object with capability to hold internal stats as a JSON endpoint. 75 type HTTP struct { 76 local *Local 77 log log.Modular 78 timestamp time.Time 79 pathPrefix string 80 pathMapping *pathMapping 81 } 82 83 // NewHTTP creates and returns a new HTTP object. 84 func NewHTTP(config Config, opts ...func(Type)) (Type, error) { 85 t := &HTTP{ 86 local: NewLocal(), 87 timestamp: time.Now(), 88 pathPrefix: config.HTTP.Prefix, 89 } 90 for _, opt := range opts { 91 opt(t) 92 } 93 var err error 94 if t.pathMapping, err = newPathMapping(config.HTTP.PathMapping, t.log); err != nil { 95 return nil, fmt.Errorf("failed to init path mapping: %v", err) 96 } 97 return t, nil 98 } 99 100 //------------------------------------------------------------------------------ 101 102 func (h *HTTP) getPath(path string) string { 103 path = h.pathMapping.mapPathNoTags(path) 104 if len(h.pathPrefix) > 0 && len(path) > 0 { 105 path = h.pathPrefix + "." + path 106 } 107 return path 108 } 109 110 // HandlerFunc returns an http.HandlerFunc for accessing metrics as a JSON blob. 111 func (h *HTTP) HandlerFunc() http.HandlerFunc { 112 return func(w http.ResponseWriter, r *http.Request) { 113 uptime := time.Since(h.timestamp).String() 114 goroutines := runtime.NumGoroutine() 115 116 counters := h.local.GetCounters() 117 timings := h.local.GetTimings() 118 119 obj := gabs.New() 120 for k, v := range counters { 121 obj.SetP(v, k) 122 } 123 for k, v := range timings { 124 obj.SetP(v, k) 125 obj.SetP(time.Duration(v).String(), k+"_readable") 126 } 127 obj.SetP(uptime, "uptime") 128 obj.SetP(goroutines, "goroutines") 129 130 w.Header().Set("Content-Type", "application/json") 131 w.Write(obj.Bytes()) 132 } 133 } 134 135 // GetCounter returns a stat counter object for a path. 136 func (h *HTTP) GetCounter(path string) StatCounter { 137 if path = h.getPath(path); path == "" { 138 return DudStat{} 139 } 140 return h.local.GetCounter(path) 141 } 142 143 // GetCounterVec returns a stat counter object for a path with the labels 144 // discarded. 145 func (h *HTTP) GetCounterVec(path string, n []string) StatCounterVec { 146 if path = h.getPath(path); path == "" { 147 return fakeCounterVec(func([]string) StatCounter { 148 return DudStat{} 149 }) 150 } 151 return fakeCounterVec(func([]string) StatCounter { 152 return h.local.GetCounter(path) 153 }) 154 } 155 156 // GetTimer returns a stat timer object for a path. 157 func (h *HTTP) GetTimer(path string) StatTimer { 158 if path = h.getPath(path); path == "" { 159 return DudStat{} 160 } 161 return h.local.GetTimer(path) 162 } 163 164 // GetTimerVec returns a stat timer object for a path with the labels 165 // discarded. 166 func (h *HTTP) GetTimerVec(path string, n []string) StatTimerVec { 167 if path = h.getPath(path); path == "" { 168 return fakeTimerVec(func([]string) StatTimer { 169 return DudStat{} 170 }) 171 } 172 return fakeTimerVec(func([]string) StatTimer { 173 return h.local.GetTimer(path) 174 }) 175 } 176 177 // GetGauge returns a stat gauge object for a path. 178 func (h *HTTP) GetGauge(path string) StatGauge { 179 if path = h.getPath(path); path == "" { 180 return DudStat{} 181 } 182 return h.local.GetGauge(path) 183 } 184 185 // GetGaugeVec returns a stat timer object for a path with the labels 186 // discarded. 187 func (h *HTTP) GetGaugeVec(path string, n []string) StatGaugeVec { 188 if path = h.getPath(path); path == "" { 189 return fakeGaugeVec(func([]string) StatGauge { 190 return DudStat{} 191 }) 192 } 193 return fakeGaugeVec(func([]string) StatGauge { 194 return h.local.GetGauge(path) 195 }) 196 } 197 198 // SetLogger does nothing. 199 func (h *HTTP) SetLogger(log log.Modular) { 200 h.log = log 201 } 202 203 // Close stops the HTTP object from aggregating metrics and cleans up resources. 204 func (h *HTTP) Close() error { 205 return nil 206 } 207 208 //------------------------------------------------------------------------------