config.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. // Copyright The OpenTelemetry Authors
  2. // SPDX-License-Identifier: Apache-2.0
  3. package prometheusreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver"
  4. import (
  5. "errors"
  6. "fmt"
  7. "net/url"
  8. "os"
  9. "sort"
  10. "strings"
  11. "time"
  12. commonconfig "github.com/prometheus/common/config"
  13. promconfig "github.com/prometheus/prometheus/config"
  14. promHTTP "github.com/prometheus/prometheus/discovery/http"
  15. "github.com/prometheus/prometheus/discovery/kubernetes"
  16. "go.opentelemetry.io/collector/component"
  17. "go.opentelemetry.io/collector/confmap"
  18. "go.uber.org/zap"
  19. "gopkg.in/yaml.v2"
  20. )
  21. const (
  22. // The key for Prometheus scraping configs.
  23. prometheusConfigKey = "config"
  24. // keys to access the http_sd_config from config root
  25. targetAllocatorConfigKey = "target_allocator"
  26. targetAllocatorHTTPSDConfigKey = "http_sd_config"
  27. )
  28. // Config defines configuration for Prometheus receiver.
  29. type Config struct {
  30. PrometheusConfig *promconfig.Config `mapstructure:"-"`
  31. TrimMetricSuffixes bool `mapstructure:"trim_metric_suffixes"`
  32. // UseStartTimeMetric enables retrieving the start time of all counter metrics
  33. // from the process_start_time_seconds metric. This is only correct if all counters on that endpoint
  34. // started after the process start time, and the process is the only actor exporting the metric after
  35. // the process started. It should not be used in "exporters" which export counters that may have
  36. // started before the process itself. Use only if you know what you are doing, as this may result
  37. // in incorrect rate calculations.
  38. UseStartTimeMetric bool `mapstructure:"use_start_time_metric"`
  39. StartTimeMetricRegex string `mapstructure:"start_time_metric_regex"`
  40. // ReportExtraScrapeMetrics - enables reporting of additional metrics for Prometheus client like scrape_body_size_bytes
  41. ReportExtraScrapeMetrics bool `mapstructure:"report_extra_scrape_metrics"`
  42. TargetAllocator *targetAllocator `mapstructure:"target_allocator"`
  43. // ConfigPlaceholder is just an entry to make the configuration pass a check
  44. // that requires that all keys present in the config actually exist on the
  45. // structure, ie.: it will error if an unknown key is present.
  46. ConfigPlaceholder any `mapstructure:"config"`
  47. // EnableProtobufNegotiation allows the collector to set the scraper option for
  48. // protobuf negotiation when conferring with a prometheus client.
  49. EnableProtobufNegotiation bool `mapstructure:"enable_protobuf_negotiation"`
  50. }
  51. type targetAllocator struct {
  52. Endpoint string `mapstructure:"endpoint"`
  53. Interval time.Duration `mapstructure:"interval"`
  54. CollectorID string `mapstructure:"collector_id"`
  55. // ConfigPlaceholder is just an entry to make the configuration pass a check
  56. // that requires that all keys present in the config actually exist on the
  57. // structure, ie.: it will error if an unknown key is present.
  58. ConfigPlaceholder any `mapstructure:"http_sd_config"`
  59. HTTPSDConfig *promHTTP.SDConfig `mapstructure:"-"`
  60. }
  61. var _ component.Config = (*Config)(nil)
  62. var _ confmap.Unmarshaler = (*Config)(nil)
  63. func checkFile(fn string) error {
  64. // Nothing set, nothing to error on.
  65. if fn == "" {
  66. return nil
  67. }
  68. _, err := os.Stat(fn)
  69. return err
  70. }
  71. func checkTLSConfig(tlsConfig commonconfig.TLSConfig) error {
  72. if err := checkFile(tlsConfig.CertFile); err != nil {
  73. return fmt.Errorf("error checking client cert file %q: %w", tlsConfig.CertFile, err)
  74. }
  75. if err := checkFile(tlsConfig.KeyFile); err != nil {
  76. return fmt.Errorf("error checking client key file %q: %w", tlsConfig.KeyFile, err)
  77. }
  78. return nil
  79. }
  80. // Validate checks the receiver configuration is valid.
  81. func (cfg *Config) Validate() error {
  82. promConfig := cfg.PrometheusConfig
  83. if promConfig != nil {
  84. err := cfg.validatePromConfig(promConfig)
  85. if err != nil {
  86. return err
  87. }
  88. }
  89. if cfg.TargetAllocator != nil {
  90. err := cfg.validateTargetAllocatorConfig()
  91. if err != nil {
  92. return err
  93. }
  94. }
  95. return nil
  96. }
  97. func (cfg *Config) validatePromConfig(promConfig *promconfig.Config) error {
  98. if len(promConfig.ScrapeConfigs) == 0 && cfg.TargetAllocator == nil {
  99. return errors.New("no Prometheus scrape_configs or target_allocator set")
  100. }
  101. // Reject features that Prometheus supports but that the receiver doesn't support:
  102. // See:
  103. // * https://github.com/open-telemetry/opentelemetry-collector/issues/3863
  104. // * https://github.com/open-telemetry/wg-prometheus/issues/3
  105. unsupportedFeatures := make([]string, 0, 4)
  106. if len(promConfig.RemoteWriteConfigs) != 0 {
  107. unsupportedFeatures = append(unsupportedFeatures, "remote_write")
  108. }
  109. if len(promConfig.RemoteReadConfigs) != 0 {
  110. unsupportedFeatures = append(unsupportedFeatures, "remote_read")
  111. }
  112. if len(promConfig.RuleFiles) != 0 {
  113. unsupportedFeatures = append(unsupportedFeatures, "rule_files")
  114. }
  115. if len(promConfig.AlertingConfig.AlertRelabelConfigs) != 0 {
  116. unsupportedFeatures = append(unsupportedFeatures, "alert_config.relabel_configs")
  117. }
  118. if len(promConfig.AlertingConfig.AlertmanagerConfigs) != 0 {
  119. unsupportedFeatures = append(unsupportedFeatures, "alert_config.alertmanagers")
  120. }
  121. if len(unsupportedFeatures) != 0 {
  122. // Sort the values for deterministic error messages.
  123. sort.Strings(unsupportedFeatures)
  124. return fmt.Errorf("unsupported features:\n\t%s", strings.Join(unsupportedFeatures, "\n\t"))
  125. }
  126. for _, sc := range cfg.PrometheusConfig.ScrapeConfigs {
  127. if sc.HTTPClientConfig.Authorization != nil {
  128. if err := checkFile(sc.HTTPClientConfig.Authorization.CredentialsFile); err != nil {
  129. return fmt.Errorf("error checking authorization credentials file %q: %w", sc.HTTPClientConfig.Authorization.CredentialsFile, err)
  130. }
  131. }
  132. if err := checkTLSConfig(sc.HTTPClientConfig.TLSConfig); err != nil {
  133. return err
  134. }
  135. for _, c := range sc.ServiceDiscoveryConfigs {
  136. if c, ok := c.(*kubernetes.SDConfig); ok {
  137. if err := checkTLSConfig(c.HTTPClientConfig.TLSConfig); err != nil {
  138. return err
  139. }
  140. }
  141. }
  142. }
  143. return nil
  144. }
  145. func (cfg *Config) validateTargetAllocatorConfig() error {
  146. // validate targetAllocator
  147. targetAllocatorConfig := cfg.TargetAllocator
  148. if targetAllocatorConfig == nil {
  149. return nil
  150. }
  151. // ensure valid endpoint
  152. if _, err := url.ParseRequestURI(targetAllocatorConfig.Endpoint); err != nil {
  153. return fmt.Errorf("TargetAllocator endpoint is not valid: %s", targetAllocatorConfig.Endpoint)
  154. }
  155. // ensure valid collectorID without variables
  156. if targetAllocatorConfig.CollectorID == "" || strings.Contains(targetAllocatorConfig.CollectorID, "${") {
  157. return fmt.Errorf("CollectorID is not a valid ID")
  158. }
  159. return nil
  160. }
  161. // Unmarshal a config.Parser into the config struct.
  162. func (cfg *Config) Unmarshal(componentParser *confmap.Conf) error {
  163. if componentParser == nil {
  164. return nil
  165. }
  166. // We need custom unmarshaling because prometheus "config" subkey defines its own
  167. // YAML unmarshaling routines so we need to do it explicitly.
  168. err := componentParser.Unmarshal(cfg, confmap.WithErrorUnused())
  169. if err != nil {
  170. return fmt.Errorf("prometheus receiver failed to parse config: %w", err)
  171. }
  172. // Unmarshal prometheus's config values. Since prometheus uses `yaml` tags, so use `yaml`.
  173. promCfg, err := componentParser.Sub(prometheusConfigKey)
  174. if err != nil || len(promCfg.ToStringMap()) == 0 {
  175. return err
  176. }
  177. out, err := yaml.Marshal(promCfg.ToStringMap())
  178. if err != nil {
  179. return fmt.Errorf("prometheus receiver failed to marshal config to yaml: %w", err)
  180. }
  181. err = yaml.UnmarshalStrict(out, &cfg.PrometheusConfig)
  182. if err != nil {
  183. return fmt.Errorf("prometheus receiver failed to unmarshal yaml to prometheus config: %w", err)
  184. }
  185. // Unmarshal targetAllocator configs
  186. targetAllocatorCfg, err := componentParser.Sub(targetAllocatorConfigKey)
  187. if err != nil {
  188. return err
  189. }
  190. targetAllocatorHTTPSDCfg, err := targetAllocatorCfg.Sub(targetAllocatorHTTPSDConfigKey)
  191. if err != nil {
  192. return err
  193. }
  194. targetAllocatorHTTPSDMap := targetAllocatorHTTPSDCfg.ToStringMap()
  195. if len(targetAllocatorHTTPSDMap) != 0 {
  196. targetAllocatorHTTPSDMap["url"] = "http://placeholder" // we have to set it as else the marshal will fail
  197. httpSDConf, err := yaml.Marshal(targetAllocatorHTTPSDMap)
  198. if err != nil {
  199. return fmt.Errorf("prometheus receiver failed to marshal config to yaml: %w", err)
  200. }
  201. err = yaml.UnmarshalStrict(httpSDConf, &cfg.TargetAllocator.HTTPSDConfig)
  202. if err != nil {
  203. return fmt.Errorf("prometheus receiver failed to unmarshal yaml to prometheus config: %w", err)
  204. }
  205. }
  206. return nil
  207. }
  208. func configWarnings(logger *zap.Logger, cfg *Config) {
  209. for _, sc := range cfg.PrometheusConfig.ScrapeConfigs {
  210. for _, rc := range sc.MetricRelabelConfigs {
  211. if rc.TargetLabel == "__name__" {
  212. logger.Warn("metric renaming using metric_relabel_configs will result in unknown-typed metrics without a unit or description", zap.String("job", sc.JobName))
  213. }
  214. }
  215. }
  216. }