github.com/Jeffail/benthos/v3@v3.65.0/lib/metrics/constructor.go (about)

     1  package metrics
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/Jeffail/benthos/v3/internal/docs"
    12  	"github.com/Jeffail/benthos/v3/lib/log"
    13  	"github.com/Jeffail/benthos/v3/lib/util/config"
    14  	"gopkg.in/yaml.v3"
    15  )
    16  
    17  //------------------------------------------------------------------------------
    18  
    19  // Errors for the metrics package.
    20  var (
    21  	ErrInvalidMetricOutputType = errors.New("invalid metrics output type")
    22  )
    23  
    24  //------------------------------------------------------------------------------
    25  
    26  // TypeSpec is a constructor and a usage description for each metric output
    27  // type.
    28  type TypeSpec struct {
    29  	constructor func(conf Config, opts ...func(Type)) (Type, error)
    30  
    31  	Status      docs.Status
    32  	Version     string
    33  	Summary     string
    34  	Description string
    35  	Footnotes   string
    36  	config      docs.FieldSpec
    37  	FieldSpecs  docs.FieldSpecs
    38  }
    39  
    40  // ConstructorFunc is a func signature able to construct a metrics output.
    41  type ConstructorFunc func(Config, ...func(Type)) (Type, error)
    42  
    43  // WalkConstructors iterates each component constructor.
    44  func WalkConstructors(fn func(ConstructorFunc, docs.ComponentSpec)) {
    45  	inferred := docs.ComponentFieldsFromConf(NewConfig())
    46  	for k, v := range Constructors {
    47  		conf := v.config
    48  		if len(v.FieldSpecs) > 0 {
    49  			conf = docs.FieldComponent().WithChildren(v.FieldSpecs.DefaultAndTypeFrom(inferred[k])...)
    50  		} else {
    51  			conf.Children = conf.Children.DefaultAndTypeFrom(inferred[k])
    52  		}
    53  		spec := docs.ComponentSpec{
    54  			Type:        docs.TypeMetrics,
    55  			Name:        k,
    56  			Summary:     v.Summary,
    57  			Description: v.Description,
    58  			Footnotes:   v.Footnotes,
    59  			Config:      conf,
    60  			Status:      v.Status,
    61  			Version:     v.Version,
    62  		}
    63  		fn(ConstructorFunc(v.constructor), spec)
    64  	}
    65  }
    66  
    67  // Constructors is a map of all metrics types with their specs.
    68  var Constructors = map[string]TypeSpec{}
    69  
    70  //------------------------------------------------------------------------------
    71  
    72  // String constants representing each metric type.
    73  const (
    74  	TypeAWSCloudWatch = "aws_cloudwatch"
    75  	TypeBlackList     = "blacklist"
    76  	TypeCloudWatch    = "cloudwatch"
    77  	TypeHTTPServer    = "http_server"
    78  	TypeInfluxDB      = "influxdb"
    79  	TypeNone          = "none"
    80  	TypePrometheus    = "prometheus"
    81  	TypeRename        = "rename"
    82  	TypeStatsd        = "statsd"
    83  	TypeStdout        = "stdout"
    84  	TypeWhiteList     = "whitelist"
    85  )
    86  
    87  //------------------------------------------------------------------------------
    88  
    89  // Config is the all encompassing configuration struct for all metric output
    90  // types.
    91  type Config struct {
    92  	Type          string           `json:"type" yaml:"type"`
    93  	AWSCloudWatch CloudWatchConfig `json:"aws_cloudwatch" yaml:"aws_cloudwatch"`
    94  	Blacklist     BlacklistConfig  `json:"blacklist" yaml:"blacklist"`
    95  	CloudWatch    CloudWatchConfig `json:"cloudwatch" yaml:"cloudwatch"`
    96  	HTTP          HTTPConfig       `json:"http_server" yaml:"http_server"`
    97  	InfluxDB      InfluxDBConfig   `json:"influxdb" yaml:"influxdb"`
    98  	None          struct{}         `json:"none" yaml:"none"`
    99  	Prometheus    PrometheusConfig `json:"prometheus" yaml:"prometheus"`
   100  	Rename        RenameConfig     `json:"rename" yaml:"rename"`
   101  	Statsd        StatsdConfig     `json:"statsd" yaml:"statsd"`
   102  	Stdout        StdoutConfig     `json:"stdout" yaml:"stdout"`
   103  	Whitelist     WhitelistConfig  `json:"whitelist" yaml:"whitelist"`
   104  }
   105  
   106  // NewConfig returns a configuration struct fully populated with default values.
   107  func NewConfig() Config {
   108  	return Config{
   109  		Type:          "http_server",
   110  		AWSCloudWatch: NewCloudWatchConfig(),
   111  		Blacklist:     NewBlacklistConfig(),
   112  		CloudWatch:    NewCloudWatchConfig(),
   113  		HTTP:          NewHTTPConfig(),
   114  		InfluxDB:      NewInfluxDBConfig(),
   115  		None:          struct{}{},
   116  		Prometheus:    NewPrometheusConfig(),
   117  		Rename:        NewRenameConfig(),
   118  		Statsd:        NewStatsdConfig(),
   119  		Stdout:        NewStdoutConfig(),
   120  		Whitelist:     NewWhitelistConfig(),
   121  	}
   122  }
   123  
   124  // SanitiseConfig returns a sanitised version of the Config, meaning sections
   125  // that aren't relevant to behaviour are removed.
   126  func SanitiseConfig(conf Config) (interface{}, error) {
   127  	return conf.Sanitised(false)
   128  }
   129  
   130  // Sanitised returns a sanitised version of the config, meaning sections that
   131  // aren't relevant to behaviour are removed. Also optionally removes deprecated
   132  // fields.
   133  func (conf Config) Sanitised(removeDeprecated bool) (interface{}, error) {
   134  	outputMap, err := config.SanitizeComponent(conf)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	if err := docs.SanitiseComponentConfig(
   139  		docs.TypeMetrics,
   140  		map[string]interface{}(outputMap),
   141  		docs.ShouldDropDeprecated(removeDeprecated),
   142  	); err != nil {
   143  		return nil, err
   144  	}
   145  	return outputMap, nil
   146  }
   147  
   148  //------------------------------------------------------------------------------
   149  
   150  // UnmarshalJSON ensures that when parsing configs that are in a map or slice
   151  // the default values are still applied.
   152  func (conf *Config) UnmarshalJSON(bytes []byte) error {
   153  	type confAlias Config
   154  	aliased := confAlias(NewConfig())
   155  
   156  	if err := json.Unmarshal(bytes, &aliased); err != nil {
   157  		return err
   158  	}
   159  
   160  	*conf = Config(aliased)
   161  	return nil
   162  }
   163  
   164  // UnmarshalYAML ensures that when parsing configs that are in a map or slice
   165  // the default values are still applied.
   166  func (conf *Config) UnmarshalYAML(value *yaml.Node) error {
   167  	type confAlias Config
   168  	aliased := confAlias(NewConfig())
   169  
   170  	err := value.Decode(&aliased)
   171  	if err != nil {
   172  		return fmt.Errorf("line %v: %v", value.Line, err)
   173  	}
   174  
   175  	if aliased.Type, _, err = docs.GetInferenceCandidateFromYAML(nil, docs.TypeMetrics, aliased.Type, value); err != nil {
   176  		return fmt.Errorf("line %v: %w", value.Line, err)
   177  	}
   178  
   179  	*conf = Config(aliased)
   180  	return nil
   181  }
   182  
   183  //------------------------------------------------------------------------------
   184  
   185  // OptSetLogger sets the logging output to be used by the metrics clients.
   186  func OptSetLogger(log log.Modular) func(Type) {
   187  	return func(t Type) {
   188  		t.SetLogger(log)
   189  	}
   190  }
   191  
   192  //------------------------------------------------------------------------------
   193  
   194  var header = "This document was generated with `benthos --list-metrics`" + `
   195  
   196  A metrics type represents a destination for Benthos metrics to be aggregated
   197  such as Statsd, Prometheus, or for debugging purposes an HTTP endpoint that
   198  exposes a JSON object of metrics.
   199  
   200  A metrics config section looks like this:
   201  
   202  ` + "``` yaml" + `
   203  metrics:
   204    statsd:
   205      prefix: foo
   206      address: localhost:8125
   207      flush_period: 100ms
   208  ` + "```" + `
   209  
   210  Benthos exposes lots of metrics and their paths will depend on your pipeline
   211  configuration. However, there are some critical metrics that will always be
   212  present that are outlined in [this document](#paths).`
   213  
   214  // Descriptions returns a formatted string of collated descriptions of each
   215  // type.
   216  func Descriptions() string {
   217  	// Order our input types alphabetically
   218  	names := []string{}
   219  	for name := range Constructors {
   220  		names = append(names, name)
   221  	}
   222  	sort.Strings(names)
   223  
   224  	buf := bytes.Buffer{}
   225  	buf.WriteString("Metric Target Types\n")
   226  	buf.WriteString(strings.Repeat("=", 19))
   227  	buf.WriteString("\n\n")
   228  	buf.WriteString(header)
   229  	buf.WriteString("\n\n")
   230  
   231  	// Append each description
   232  	for i, name := range names {
   233  		var confBytes []byte
   234  
   235  		conf := NewConfig()
   236  		conf.Type = name
   237  		if confSanit, err := SanitiseConfig(conf); err == nil {
   238  			confBytes, _ = config.MarshalYAML(confSanit)
   239  		}
   240  
   241  		buf.WriteString("## ")
   242  		buf.WriteString("`" + name + "`")
   243  		buf.WriteString("\n")
   244  		if confBytes != nil {
   245  			buf.WriteString("\n``` yaml\n")
   246  			buf.Write(confBytes)
   247  			buf.WriteString("```\n")
   248  		}
   249  		buf.WriteString(Constructors[name].Description)
   250  		buf.WriteString("\n")
   251  		if i != (len(names) - 1) {
   252  			buf.WriteString("\n---\n")
   253  		}
   254  	}
   255  	return buf.String()
   256  }
   257  
   258  // New creates a metric output type based on a configuration.
   259  func New(conf Config, opts ...func(Type)) (Type, error) {
   260  	if c, ok := Constructors[conf.Type]; ok {
   261  		return c.constructor(conf, opts...)
   262  	}
   263  	return nil, ErrInvalidMetricOutputType
   264  }
   265  
   266  //------------------------------------------------------------------------------