github.com/Jeffail/benthos/v3@v3.65.0/lib/metrics/statsd.go (about) 1 package metrics 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/Jeffail/benthos/v3/internal/docs" 8 "github.com/Jeffail/benthos/v3/lib/log" 9 statsd "github.com/smira/go-statsd" 10 ) 11 12 //------------------------------------------------------------------------------ 13 14 func init() { 15 Constructors[TypeStatsd] = TypeSpec{ 16 constructor: NewStatsd, 17 Summary: ` 18 Pushes metrics using the [StatsD protocol](https://github.com/statsd/statsd). 19 Supported tagging formats are 'legacy', 'none', 'datadog' and 'influxdb'.`, 20 Description: ` 21 The underlying client library has recently been updated in order to support 22 tagging. The tag format 'legacy' is default and causes Benthos to continue using 23 the old library in order to preserve backwards compatibility. 24 25 The legacy library aggregated timing metrics, so dashboards and alerts may need 26 to be updated when migrating to the new library. 27 28 The 'network' field is deprecated and scheduled for removal. If you currently 29 rely on sending Statsd metrics over TCP and want it to be supported long term 30 please [raise an issue](https://github.com/Jeffail/benthos/issues).`, 31 FieldSpecs: docs.FieldSpecs{ 32 docs.FieldCommon("prefix", "A string prefix to add to all metrics."), 33 pathMappingDocs(false, false), 34 docs.FieldCommon("address", "The address to send metrics to."), 35 docs.FieldCommon("flush_period", "The time interval between metrics flushes."), 36 docs.FieldCommon("tag_format", "Metrics tagging is supported in a variety of formats. The format 'legacy' is a special case that forces Benthos to use a deprecated library for backwards compatibility.").HasOptions( 37 "none", "datadog", "influxdb", "legacy", 38 ), 39 docs.FieldDeprecated("network"), 40 }, 41 } 42 } 43 44 //------------------------------------------------------------------------------ 45 46 type wrappedDatadogLogger struct { 47 log log.Modular 48 } 49 50 func (s wrappedDatadogLogger) Printf(msg string, args ...interface{}) { 51 s.log.Warnf(fmt.Sprintf(msg, args...)) 52 } 53 54 //------------------------------------------------------------------------------ 55 56 // StatsdConfig is config for the Statsd metrics type. 57 type StatsdConfig struct { 58 Prefix string `json:"prefix" yaml:"prefix"` 59 PathMapping string `json:"path_mapping" yaml:"path_mapping"` 60 Address string `json:"address" yaml:"address"` 61 FlushPeriod string `json:"flush_period" yaml:"flush_period"` 62 Network string `json:"network" yaml:"network"` 63 TagFormat string `json:"tag_format" yaml:"tag_format"` 64 } 65 66 // NewStatsdConfig creates an StatsdConfig struct with default values. 67 func NewStatsdConfig() StatsdConfig { 68 return StatsdConfig{ 69 Prefix: "benthos", 70 PathMapping: "", 71 Address: "localhost:4040", 72 FlushPeriod: "100ms", 73 Network: "udp", 74 TagFormat: TagFormatLegacy, 75 } 76 } 77 78 // Tag formats supported by the statsd metric type. 79 const ( 80 TagFormatNone = "none" 81 TagFormatDatadog = "datadog" 82 TagFormatInfluxDB = "influxdb" 83 TagFormatLegacy = "legacy" 84 ) 85 86 //------------------------------------------------------------------------------ 87 88 // StatsdStat is a representation of a single metric stat. Interactions with 89 // this stat are thread safe. 90 type StatsdStat struct { 91 path string 92 s *statsd.Client 93 tags []statsd.Tag 94 } 95 96 // Incr increments a metric by an amount. 97 func (s *StatsdStat) Incr(count int64) error { 98 s.s.Incr(s.path, count, s.tags...) 99 return nil 100 } 101 102 // Decr decrements a metric by an amount. 103 func (s *StatsdStat) Decr(count int64) error { 104 s.s.Decr(s.path, count, s.tags...) 105 return nil 106 } 107 108 // Timing sets a timing metric. 109 func (s *StatsdStat) Timing(delta int64) error { 110 s.s.Timing(s.path, delta, s.tags...) 111 return nil 112 } 113 114 // Set sets a gauge metric. 115 func (s *StatsdStat) Set(value int64) error { 116 s.s.Gauge(s.path, value, s.tags...) 117 return nil 118 } 119 120 //------------------------------------------------------------------------------ 121 122 // Statsd is a stats object with capability to hold internal stats as a JSON 123 // endpoint. 124 type Statsd struct { 125 config Config 126 s *statsd.Client 127 log log.Modular 128 pathMapping *pathMapping 129 } 130 131 // NewStatsd creates and returns a new Statsd object. 132 func NewStatsd(config Config, opts ...func(Type)) (Type, error) { 133 if config.Statsd.Network != "udp" || config.Statsd.TagFormat == TagFormatLegacy { 134 return NewStatsdLegacy(config, opts...) 135 } 136 137 flushPeriod, err := time.ParseDuration(config.Statsd.FlushPeriod) 138 if err != nil { 139 return nil, fmt.Errorf("failed to parse flush period: %s", err) 140 } 141 142 s := &Statsd{ 143 config: config, 144 log: log.Noop(), 145 } 146 for _, opt := range opts { 147 opt(s) 148 } 149 150 if s.pathMapping, err = newPathMapping(config.Statsd.PathMapping, s.log); err != nil { 151 return nil, fmt.Errorf("failed to init path mapping: %v", err) 152 } 153 154 prefix := config.Statsd.Prefix 155 if len(prefix) > 0 && prefix[len(prefix)-1] != '.' { 156 prefix += "." 157 } 158 159 statsdOpts := []statsd.Option{ 160 statsd.FlushInterval(flushPeriod), 161 statsd.MetricPrefix(prefix), 162 statsd.Logger(wrappedDatadogLogger{log: s.log}), 163 } 164 165 switch config.Statsd.TagFormat { 166 case TagFormatInfluxDB: 167 statsdOpts = append(statsdOpts, statsd.TagStyle(statsd.TagFormatInfluxDB)) 168 case TagFormatDatadog: 169 statsdOpts = append(statsdOpts, statsd.TagStyle(statsd.TagFormatDatadog)) 170 case TagFormatNone: 171 default: 172 return nil, fmt.Errorf("tag format '%s' was not recognised", config.Statsd.TagFormat) 173 } 174 175 client := statsd.NewClient(config.Statsd.Address, statsdOpts...) 176 177 s.s = client 178 return s, nil 179 } 180 181 //------------------------------------------------------------------------------ 182 183 // GetCounter returns a stat counter object for a path. 184 func (h *Statsd) GetCounter(path string) StatCounter { 185 if path = h.pathMapping.mapPathNoTags(path); path == "" { 186 return DudStat{} 187 } 188 return &StatsdStat{ 189 path: path, 190 s: h.s, 191 } 192 } 193 194 // GetCounterVec returns a stat counter object for a path with the labels 195 func (h *Statsd) GetCounterVec(path string, n []string) StatCounterVec { 196 if path = h.pathMapping.mapPathNoTags(path); path == "" { 197 return fakeCounterVec(func([]string) StatCounter { 198 return DudStat{} 199 }) 200 } 201 return &fCounterVec{ 202 f: func(l []string) StatCounter { 203 return &StatsdStat{ 204 path: path, 205 s: h.s, 206 tags: tags(n, l), 207 } 208 }, 209 } 210 } 211 212 // GetTimer returns a stat timer object for a path. 213 func (h *Statsd) GetTimer(path string) StatTimer { 214 if path = h.pathMapping.mapPathNoTags(path); path == "" { 215 return DudStat{} 216 } 217 return &StatsdStat{ 218 path: path, 219 s: h.s, 220 } 221 } 222 223 // GetTimerVec returns a stat timer object for a path with the labels 224 func (h *Statsd) GetTimerVec(path string, n []string) StatTimerVec { 225 if path = h.pathMapping.mapPathNoTags(path); path == "" { 226 return fakeTimerVec(func([]string) StatTimer { 227 return DudStat{} 228 }) 229 } 230 return &fTimerVec{ 231 f: func(l []string) StatTimer { 232 return &StatsdStat{ 233 path: path, 234 s: h.s, 235 tags: tags(n, l), 236 } 237 }, 238 } 239 } 240 241 // GetGauge returns a stat gauge object for a path. 242 func (h *Statsd) GetGauge(path string) StatGauge { 243 if path = h.pathMapping.mapPathNoTags(path); path == "" { 244 return DudStat{} 245 } 246 return &StatsdStat{ 247 path: path, 248 s: h.s, 249 } 250 } 251 252 // GetGaugeVec returns a stat timer object for a path with the labels 253 func (h *Statsd) GetGaugeVec(path string, n []string) StatGaugeVec { 254 if path = h.pathMapping.mapPathNoTags(path); path == "" { 255 return fakeGaugeVec(func([]string) StatGauge { 256 return DudStat{} 257 }) 258 } 259 return &fGaugeVec{ 260 f: func(l []string) StatGauge { 261 return &StatsdStat{ 262 path: path, 263 s: h.s, 264 tags: tags(n, l), 265 } 266 }, 267 } 268 } 269 270 // SetLogger sets the logger used to print connection errors. 271 func (h *Statsd) SetLogger(log log.Modular) { 272 h.log = log 273 } 274 275 // Close stops the Statsd object from aggregating metrics and cleans up 276 // resources. 277 func (h *Statsd) Close() error { 278 h.s.Close() 279 return nil 280 } 281 282 // tags merges tag labels with their interpolated values 283 // 284 // no attempt is made to merge labels and values if slices 285 // are not the same length 286 func tags(labels, values []string) []statsd.Tag { 287 if len(labels) != len(values) { 288 return nil 289 } 290 tags := make([]statsd.Tag, len(labels)) 291 for i := range labels { 292 tags[i] = statsd.StringTag(labels[i], values[i]) 293 } 294 return tags 295 } 296 297 //------------------------------------------------------------------------------