123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- // Copyright The OpenTelemetry Authors
- // SPDX-License-Identifier: Apache-2.0
- package prometheusreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver"
- import (
- "errors"
- "fmt"
- "net/url"
- "os"
- "sort"
- "strings"
- "time"
- commonconfig "github.com/prometheus/common/config"
- promconfig "github.com/prometheus/prometheus/config"
- promHTTP "github.com/prometheus/prometheus/discovery/http"
- "github.com/prometheus/prometheus/discovery/kubernetes"
- "go.opentelemetry.io/collector/component"
- "go.opentelemetry.io/collector/confmap"
- "go.uber.org/zap"
- "gopkg.in/yaml.v2"
- )
- const (
- // The key for Prometheus scraping configs.
- prometheusConfigKey = "config"
- // keys to access the http_sd_config from config root
- targetAllocatorConfigKey = "target_allocator"
- targetAllocatorHTTPSDConfigKey = "http_sd_config"
- )
- // Config defines configuration for Prometheus receiver.
- type Config struct {
- PrometheusConfig *promconfig.Config `mapstructure:"-"`
- TrimMetricSuffixes bool `mapstructure:"trim_metric_suffixes"`
- // UseStartTimeMetric enables retrieving the start time of all counter metrics
- // from the process_start_time_seconds metric. This is only correct if all counters on that endpoint
- // started after the process start time, and the process is the only actor exporting the metric after
- // the process started. It should not be used in "exporters" which export counters that may have
- // started before the process itself. Use only if you know what you are doing, as this may result
- // in incorrect rate calculations.
- UseStartTimeMetric bool `mapstructure:"use_start_time_metric"`
- StartTimeMetricRegex string `mapstructure:"start_time_metric_regex"`
- // ReportExtraScrapeMetrics - enables reporting of additional metrics for Prometheus client like scrape_body_size_bytes
- ReportExtraScrapeMetrics bool `mapstructure:"report_extra_scrape_metrics"`
- TargetAllocator *targetAllocator `mapstructure:"target_allocator"`
- // ConfigPlaceholder is just an entry to make the configuration pass a check
- // that requires that all keys present in the config actually exist on the
- // structure, ie.: it will error if an unknown key is present.
- ConfigPlaceholder any `mapstructure:"config"`
- // EnableProtobufNegotiation allows the collector to set the scraper option for
- // protobuf negotiation when conferring with a prometheus client.
- EnableProtobufNegotiation bool `mapstructure:"enable_protobuf_negotiation"`
- }
- type targetAllocator struct {
- Endpoint string `mapstructure:"endpoint"`
- Interval time.Duration `mapstructure:"interval"`
- CollectorID string `mapstructure:"collector_id"`
- // ConfigPlaceholder is just an entry to make the configuration pass a check
- // that requires that all keys present in the config actually exist on the
- // structure, ie.: it will error if an unknown key is present.
- ConfigPlaceholder any `mapstructure:"http_sd_config"`
- HTTPSDConfig *promHTTP.SDConfig `mapstructure:"-"`
- }
- var _ component.Config = (*Config)(nil)
- var _ confmap.Unmarshaler = (*Config)(nil)
- func checkFile(fn string) error {
- // Nothing set, nothing to error on.
- if fn == "" {
- return nil
- }
- _, err := os.Stat(fn)
- return err
- }
- func checkTLSConfig(tlsConfig commonconfig.TLSConfig) error {
- if err := checkFile(tlsConfig.CertFile); err != nil {
- return fmt.Errorf("error checking client cert file %q: %w", tlsConfig.CertFile, err)
- }
- if err := checkFile(tlsConfig.KeyFile); err != nil {
- return fmt.Errorf("error checking client key file %q: %w", tlsConfig.KeyFile, err)
- }
- return nil
- }
- // Validate checks the receiver configuration is valid.
- func (cfg *Config) Validate() error {
- promConfig := cfg.PrometheusConfig
- if promConfig != nil {
- err := cfg.validatePromConfig(promConfig)
- if err != nil {
- return err
- }
- }
- if cfg.TargetAllocator != nil {
- err := cfg.validateTargetAllocatorConfig()
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (cfg *Config) validatePromConfig(promConfig *promconfig.Config) error {
- if len(promConfig.ScrapeConfigs) == 0 && cfg.TargetAllocator == nil {
- return errors.New("no Prometheus scrape_configs or target_allocator set")
- }
- // Reject features that Prometheus supports but that the receiver doesn't support:
- // See:
- // * https://github.com/open-telemetry/opentelemetry-collector/issues/3863
- // * https://github.com/open-telemetry/wg-prometheus/issues/3
- unsupportedFeatures := make([]string, 0, 4)
- if len(promConfig.RemoteWriteConfigs) != 0 {
- unsupportedFeatures = append(unsupportedFeatures, "remote_write")
- }
- if len(promConfig.RemoteReadConfigs) != 0 {
- unsupportedFeatures = append(unsupportedFeatures, "remote_read")
- }
- if len(promConfig.RuleFiles) != 0 {
- unsupportedFeatures = append(unsupportedFeatures, "rule_files")
- }
- if len(promConfig.AlertingConfig.AlertRelabelConfigs) != 0 {
- unsupportedFeatures = append(unsupportedFeatures, "alert_config.relabel_configs")
- }
- if len(promConfig.AlertingConfig.AlertmanagerConfigs) != 0 {
- unsupportedFeatures = append(unsupportedFeatures, "alert_config.alertmanagers")
- }
- if len(unsupportedFeatures) != 0 {
- // Sort the values for deterministic error messages.
- sort.Strings(unsupportedFeatures)
- return fmt.Errorf("unsupported features:\n\t%s", strings.Join(unsupportedFeatures, "\n\t"))
- }
- for _, sc := range cfg.PrometheusConfig.ScrapeConfigs {
- if sc.HTTPClientConfig.Authorization != nil {
- if err := checkFile(sc.HTTPClientConfig.Authorization.CredentialsFile); err != nil {
- return fmt.Errorf("error checking authorization credentials file %q: %w", sc.HTTPClientConfig.Authorization.CredentialsFile, err)
- }
- }
- if err := checkTLSConfig(sc.HTTPClientConfig.TLSConfig); err != nil {
- return err
- }
- for _, c := range sc.ServiceDiscoveryConfigs {
- if c, ok := c.(*kubernetes.SDConfig); ok {
- if err := checkTLSConfig(c.HTTPClientConfig.TLSConfig); err != nil {
- return err
- }
- }
- }
- }
- return nil
- }
- func (cfg *Config) validateTargetAllocatorConfig() error {
- // validate targetAllocator
- targetAllocatorConfig := cfg.TargetAllocator
- if targetAllocatorConfig == nil {
- return nil
- }
- // ensure valid endpoint
- if _, err := url.ParseRequestURI(targetAllocatorConfig.Endpoint); err != nil {
- return fmt.Errorf("TargetAllocator endpoint is not valid: %s", targetAllocatorConfig.Endpoint)
- }
- // ensure valid collectorID without variables
- if targetAllocatorConfig.CollectorID == "" || strings.Contains(targetAllocatorConfig.CollectorID, "${") {
- return fmt.Errorf("CollectorID is not a valid ID")
- }
- return nil
- }
- // Unmarshal a config.Parser into the config struct.
- func (cfg *Config) Unmarshal(componentParser *confmap.Conf) error {
- if componentParser == nil {
- return nil
- }
- // We need custom unmarshaling because prometheus "config" subkey defines its own
- // YAML unmarshaling routines so we need to do it explicitly.
- err := componentParser.Unmarshal(cfg, confmap.WithErrorUnused())
- if err != nil {
- return fmt.Errorf("prometheus receiver failed to parse config: %w", err)
- }
- // Unmarshal prometheus's config values. Since prometheus uses `yaml` tags, so use `yaml`.
- promCfg, err := componentParser.Sub(prometheusConfigKey)
- if err != nil || len(promCfg.ToStringMap()) == 0 {
- return err
- }
- out, err := yaml.Marshal(promCfg.ToStringMap())
- if err != nil {
- return fmt.Errorf("prometheus receiver failed to marshal config to yaml: %w", err)
- }
- err = yaml.UnmarshalStrict(out, &cfg.PrometheusConfig)
- if err != nil {
- return fmt.Errorf("prometheus receiver failed to unmarshal yaml to prometheus config: %w", err)
- }
- // Unmarshal targetAllocator configs
- targetAllocatorCfg, err := componentParser.Sub(targetAllocatorConfigKey)
- if err != nil {
- return err
- }
- targetAllocatorHTTPSDCfg, err := targetAllocatorCfg.Sub(targetAllocatorHTTPSDConfigKey)
- if err != nil {
- return err
- }
- targetAllocatorHTTPSDMap := targetAllocatorHTTPSDCfg.ToStringMap()
- if len(targetAllocatorHTTPSDMap) != 0 {
- targetAllocatorHTTPSDMap["url"] = "http://placeholder" // we have to set it as else the marshal will fail
- httpSDConf, err := yaml.Marshal(targetAllocatorHTTPSDMap)
- if err != nil {
- return fmt.Errorf("prometheus receiver failed to marshal config to yaml: %w", err)
- }
- err = yaml.UnmarshalStrict(httpSDConf, &cfg.TargetAllocator.HTTPSDConfig)
- if err != nil {
- return fmt.Errorf("prometheus receiver failed to unmarshal yaml to prometheus config: %w", err)
- }
- }
- return nil
- }
- func configWarnings(logger *zap.Logger, cfg *Config) {
- for _, sc := range cfg.PrometheusConfig.ScrapeConfigs {
- for _, rc := range sc.MetricRelabelConfigs {
- if rc.TargetLabel == "__name__" {
- logger.Warn("metric renaming using metric_relabel_configs will result in unknown-typed metrics without a unit or description", zap.String("job", sc.JobName))
- }
- }
- }
- }
|