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  //------------------------------------------------------------------------------