github.com/Jeffail/benthos/v3@v3.65.0/lib/metrics/rename.go (about) 1 package metrics 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "regexp" 9 "sort" 10 11 "github.com/Jeffail/benthos/v3/internal/docs" 12 "github.com/Jeffail/benthos/v3/lib/log" 13 ) 14 15 //------------------------------------------------------------------------------ 16 17 func init() { 18 Constructors[TypeRename] = TypeSpec{ 19 constructor: NewRename, 20 Status: docs.StatusDeprecated, 21 Summary: ` 22 Rename metric paths as they are registered.`, 23 FieldSpecs: docs.FieldSpecs{ 24 docs.FieldCommon("by_regexp", ` 25 A list of objects, each specifying an RE2 regular expression which will be 26 tested against each metric path registered. Each occurrence of the expression 27 will be replaced with the specified value. Inside the value $ signs are 28 interpreted as submatch expansions, e.g. $1 represents the first submatch. 29 30 The field `+"`to_label`"+` may contain any number of key/value pairs to be 31 added to a metric as labels, where the value may contain submatches from the 32 provided pattern. This allows you to extract (left-most) matched segments of the 33 renamed path into the label values.`, 34 []interface{}{ 35 map[string]interface{}{ 36 "pattern": "foo\\.([a-z]*)\\.([a-z]*)", 37 "value": "foo.$1", 38 "to_label": map[string]interface{}{ 39 "bar": "$2", 40 }, 41 }, 42 }, 43 ).WithChildren( 44 docs.FieldDeprecated("pattern"), 45 docs.FieldDeprecated("value"), 46 docs.FieldDeprecated("to_label").Map().HasType(docs.FieldTypeString), 47 ).Array(), 48 docs.FieldCommon("child", "A child metric type, this is where renamed metrics will be routed.").HasType(docs.FieldTypeMetrics), 49 }, 50 Description: ` 51 Metrics must be matched using dot notation even if the chosen output uses a 52 different form. For example, the path would be 'foo.bar' rather than 'foo_bar' 53 even when sending metrics to Prometheus. A full list of metrics paths that 54 Benthos registers can be found in [this list](/docs/components/metrics/about#paths).`, 55 Footnotes: ` 56 ## Examples 57 58 In order to replace the paths 'foo.bar.0.zap' and 'foo.baz.1.zap' with 'zip.bar' 59 and 'zip.baz' respectively, and store the respective values '0' and '1' under 60 the label key 'index' we could use this config: 61 62 ` + "```yaml" + ` 63 metrics: 64 rename: 65 by_regexp: 66 - pattern: "foo\\.([a-z]*)\\.([a-z]*)\\.zap" 67 value: "zip.$1" 68 to_label: 69 index: $2 70 child: 71 statsd: 72 prefix: foo 73 address: localhost:8125 74 ` + "```" + ` 75 76 These labels will only be injected into metrics registered without pre-existing 77 labels. Therefore it's currently not possible to combine labels registered from 78 the ` + "[`metric` processor](/docs/components/processors/metric)" + ` with labels 79 set via renaming. 80 81 ## Debugging 82 83 In order to see logs breaking down which metrics are registered and whether they 84 are renamed enable logging at the TRACE level.`, 85 } 86 } 87 88 //------------------------------------------------------------------------------ 89 90 // RenameByRegexpConfig contains config fields for a rename by regular 91 // expression pattern. 92 type RenameByRegexpConfig struct { 93 Pattern string `json:"pattern" yaml:"pattern"` 94 Value string `json:"value" yaml:"value"` 95 Labels map[string]string `json:"to_label" yaml:"to_label"` 96 } 97 98 // RenameConfig contains config fields for the Rename metric type. 99 type RenameConfig struct { 100 ByRegexp []RenameByRegexpConfig `json:"by_regexp" yaml:"by_regexp"` 101 Child *Config `json:"child" yaml:"child"` 102 } 103 104 // NewRenameConfig returns a RenameConfig with default values. 105 func NewRenameConfig() RenameConfig { 106 return RenameConfig{ 107 ByRegexp: []RenameByRegexpConfig{}, 108 Child: nil, 109 } 110 } 111 112 //------------------------------------------------------------------------------ 113 114 type dummyRenameConfig struct { 115 ByRegexp []RenameByRegexpConfig `json:"by_regexp" yaml:"by_regexp"` 116 Child interface{} `json:"child" yaml:"child"` 117 } 118 119 // MarshalJSON prints an empty object instead of nil. 120 func (w RenameConfig) MarshalJSON() ([]byte, error) { 121 dummy := dummyRenameConfig{ 122 ByRegexp: w.ByRegexp, 123 Child: w.Child, 124 } 125 if w.Child == nil { 126 dummy.Child = struct{}{} 127 } 128 return json.Marshal(dummy) 129 } 130 131 // MarshalYAML prints an empty object instead of nil. 132 func (w RenameConfig) MarshalYAML() (interface{}, error) { 133 dummy := dummyRenameConfig{ 134 ByRegexp: w.ByRegexp, 135 Child: w.Child, 136 } 137 if w.Child == nil { 138 dummy.Child = struct{}{} 139 } 140 return dummy, nil 141 } 142 143 //------------------------------------------------------------------------------ 144 145 type renameByRegexp struct { 146 expression *regexp.Regexp 147 value string 148 labels map[string]string 149 } 150 151 // Rename is a statistics object that wraps a separate statistics object 152 // and only permits statistics that pass through the whitelist to be recorded. 153 type Rename struct { 154 byRegexp []renameByRegexp 155 s Type 156 log log.Modular 157 } 158 159 // NewRename creates and returns a new Rename object 160 func NewRename(config Config, opts ...func(Type)) (Type, error) { 161 if config.Rename.Child == nil { 162 return nil, errors.New("cannot create a rename metric without a child") 163 } 164 165 child, err := New(*config.Rename.Child) 166 if err != nil { 167 return nil, err 168 } 169 170 r := &Rename{ 171 s: child, 172 log: log.Noop(), 173 } 174 175 for _, opt := range opts { 176 opt(r) 177 } 178 179 for _, p := range config.Rename.ByRegexp { 180 re, err := regexp.Compile(p.Pattern) 181 if err != nil { 182 return nil, fmt.Errorf("invalid regular expression: '%s': %v", p, err) 183 } 184 r.byRegexp = append(r.byRegexp, renameByRegexp{ 185 expression: re, 186 value: p.Value, 187 labels: p.Labels, 188 }) 189 } 190 191 return r, nil 192 } 193 194 //------------------------------------------------------------------------------ 195 196 // renamePath checks whether or not a given path is in the allowed set of 197 // paths for the Rename metrics stat. 198 func (r *Rename) renamePath(path string) (outPath string, labels map[string]string) { 199 renamed := false 200 labels = make(map[string]string) 201 for _, rr := range r.byRegexp { 202 newPath := rr.expression.ReplaceAllString(path, rr.value) 203 if newPath != path { 204 renamed = true 205 r.log.Tracef("Renamed metric path '%v' to '%v' as per regexp '%v'\n", path, newPath, rr.expression.String()) 206 } 207 if rr.labels != nil && len(rr.labels) > 0 { 208 // Extract only the matching segment of the path (left-most) 209 leftPath := rr.expression.FindString(path) 210 if len(leftPath) > 0 { 211 for k, v := range rr.labels { 212 v = rr.expression.ReplaceAllString(leftPath, v) 213 labels[k] = v 214 r.log.Tracef("Renamed label '%v' to '%v' as per regexp '%v'\n", k, v, rr.expression.String()) 215 } 216 } 217 } 218 path = newPath 219 } 220 if !renamed { 221 r.log.Tracef("Registered metric path '%v' unchanged\n", path) 222 } 223 return path, labels 224 } 225 226 //------------------------------------------------------------------------------ 227 228 func labelsFromMap(labels map[string]string) (names, values []string) { 229 names = make([]string, 0, len(labels)) 230 for k := range labels { 231 names = append(names, k) 232 } 233 sort.Strings(names) 234 values = make([]string, 0, len(names)) 235 for _, k := range names { 236 values = append(values, labels[k]) 237 } 238 return names, values 239 } 240 241 // GetCounter returns a stat counter object for a path. 242 func (r *Rename) GetCounter(path string) StatCounter { 243 rpath, labels := r.renamePath(path) 244 if len(labels) == 0 { 245 return r.s.GetCounter(rpath) 246 } 247 names, values := labelsFromMap(labels) 248 return r.s.GetCounterVec(rpath, names).With(values...) 249 } 250 251 // GetCounterVec returns a stat counter object for a path with the labels 252 // and values. 253 func (r *Rename) GetCounterVec(path string, n []string) StatCounterVec { 254 rpath, _ := r.renamePath(path) 255 return r.s.GetCounterVec(rpath, n) 256 } 257 258 // GetTimer returns a stat timer object for a path. 259 func (r *Rename) GetTimer(path string) StatTimer { 260 rpath, labels := r.renamePath(path) 261 if len(labels) == 0 { 262 return r.s.GetTimer(rpath) 263 } 264 265 names, values := labelsFromMap(labels) 266 return r.s.GetTimerVec(rpath, names).With(values...) 267 } 268 269 // GetTimerVec returns a stat timer object for a path with the labels 270 // and values. 271 func (r *Rename) GetTimerVec(path string, n []string) StatTimerVec { 272 rpath, _ := r.renamePath(path) 273 return r.s.GetTimerVec(rpath, n) 274 } 275 276 // GetGauge returns a stat gauge object for a path. 277 func (r *Rename) GetGauge(path string) StatGauge { 278 rpath, labels := r.renamePath(path) 279 if len(labels) == 0 { 280 return r.s.GetGauge(rpath) 281 } 282 283 names, values := labelsFromMap(labels) 284 return r.s.GetGaugeVec(rpath, names).With(values...) 285 } 286 287 // GetGaugeVec returns a stat timer object for a path with the labels 288 // discarded. 289 func (r *Rename) GetGaugeVec(path string, n []string) StatGaugeVec { 290 rpath, _ := r.renamePath(path) 291 return r.s.GetGaugeVec(rpath, n) 292 } 293 294 // SetLogger sets the logger used to print connection errors. 295 func (r *Rename) SetLogger(log log.Modular) { 296 r.log = log.NewModule(".rename") 297 r.s.SetLogger(log) 298 } 299 300 // Close stops the Statsd object from aggregating metrics and cleans up 301 // resources. 302 func (r *Rename) Close() error { 303 return r.s.Close() 304 } 305 306 //------------------------------------------------------------------------------ 307 308 // HandlerFunc returns an http.HandlerFunc for accessing metrics for appropriate 309 // child types 310 func (r *Rename) HandlerFunc() http.HandlerFunc { 311 if wHandlerFunc, ok := r.s.(WithHandlerFunc); ok { 312 return wHandlerFunc.HandlerFunc() 313 } 314 315 return func(w http.ResponseWriter, r *http.Request) { 316 w.WriteHeader(501) 317 w.Write([]byte("The child of this rename does not support HTTP metrics.")) 318 } 319 } 320 321 //------------------------------------------------------------------------------