Repository: prometheus/prometheus
File: config/config.go (Go)

Code before:
	// UnderscoreEscapingWithSuffixes is the default option for translating OTLP to Prometheus.
	// This option will translate metric name characters that are not alphanumerics/underscores/colons to underscores,
	// and label name characters that are not alphanumerics/underscores to underscores.
	// Unit and type suffixes may be appended to metric names, according to certain rules.
	UnderscoreEscapingWithSuffixes translationStrategyOption = "UnderscoreEscapingWithSuffixes"
)

// OTLPConfig is the configuration for writing to the OTLP endpoint.
type OTLPConfig struct {
	PromoteResourceAttributes         []string                  `yaml:"promote_resource_attributes,omitempty"`
	IgnoreResourceAttributes          []string                  `yaml:"ignore_resource_attributes,omitempty"`
	TranslationStrategy               translationStrategyOption `yaml:"translation_strategy,omitempty"`
	KeepIdentifyingResourceAttributes bool                      `yaml:"keep_identifying_resource_attributes,omitempty"`
	ConvertHistogramsToNHCB           bool                      `yaml:"convert_histograms_to_nhcb,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *OTLPConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
	*c = DefaultOTLPConfig
	type plain OTLPConfig
	if err := unmarshal((*plain)(c)); err != nil {
		return err
	}

	if c.IgnoreResourceAttributes != nil && len(c.PromoteResourceAttributes) > 0 {
		return errors.New("'ignore_resource_attributes' and 'promote_resource_attributes' cannot be configured simultaneously")
	}

	if err := validateAttributes(c.IgnoreResourceAttributes); err != nil {
		return fmt.Errorf("invalid 'ignore_resource_attributes': %w", err)
	}
	if err := validateAttributes(c.PromoteResourceAttributes); err != nil {
		return fmt.Errorf("invalid 'promote_resource_attributes': %w", err)
	}
	return nil
}

func validateAttributes(attributes []string) error {
	seen := map[string]struct{}{}
	var err error
	for i, attr := range attributes {
		attr = strings.TrimSpace(attr)
		if attr == "" {
			err = errors.Join(err, errors.New("empty OTel resource attribute"))
			continue
		}
		if _, exists := seen[attr]; exists {
			err = errors.Join(err, fmt.Errorf("duplicated OTel resource attribute %q", attr))
			continue
		}


Review comment (suggestion):
As commented on earlier, I am of the opinion that promoting all resource attributes should be an explicit configuration parameter, that is mutually exclusive with `PromoteResourceAttributes`. I don't think it's a good idea to implicitly promote all resource attributes if `ignore_resource_attributes` is configured as an empty list versus `nil`.


```suggestion
	PromoteAllResourceAttributes          []string                  `yaml:"promote_all_resource_attributes,omitempty"`
	IgnoreResourceAttributes          []string                  `yaml:"ignore_resource_attributes,omitempty"`
```

Code after:
	// This option will translate metric name characters that are not alphanumerics/underscores/colons to underscores,
	// and label name characters that are not alphanumerics/underscores to underscores.
	// Unit and type suffixes may be appended to metric names, according to certain rules.
	UnderscoreEscapingWithSuffixes translationStrategyOption = "UnderscoreEscapingWithSuffixes"
	// NoTranslation (EXPERIMENTAL): disables all translation of incoming metric
	// and label names. This offers a way for the OTLP users to use native metric names, reducing confusion.
	//
	// WARNING: This setting has significant known risks and limitations (see
	// https://prometheus.io/docs/practices/naming/  for details):
	// * Impaired UX when using PromQL in plain YAML (e.g. alerts, rules, dashboard, autoscaling configuration).
	// * Series collisions which in the best case may result in OOO errors, in the worst case a silently malformed
	// time series. For instance, you may end up in situation of ingesting `foo.bar` series with unit
	// `seconds` and a separate series `foo.bar` with unit `milliseconds`.
	//
	// As a result, this setting is experimental and currently, should not be used in
	// production systems.
	//
	// TODO(ArthurSens): Mention `type-and-unit-labels` feature (https://github.com/prometheus/proposals/pull/39) once released, as potential mitigation of the above risks.
	NoTranslation translationStrategyOption = "NoTranslation"
)

// OTLPConfig is the configuration for writing to the OTLP endpoint.
type OTLPConfig struct {
	PromoteAllResourceAttributes      bool                      `yaml:"promote_all_resource_attributes,omitempty"`
	PromoteResourceAttributes         []string                  `yaml:"promote_resource_attributes,omitempty"`
	IgnoreResourceAttributes          []string                  `yaml:"ignore_resource_attributes,omitempty"`
	TranslationStrategy               translationStrategyOption `yaml:"translation_strategy,omitempty"`
	KeepIdentifyingResourceAttributes bool                      `yaml:"keep_identifying_resource_attributes,omitempty"`
	ConvertHistogramsToNHCB           bool                      `yaml:"convert_histograms_to_nhcb,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *OTLPConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
	*c = DefaultOTLPConfig
	type plain OTLPConfig
	if err := unmarshal((*plain)(c)); err != nil {
		return err
	}

	if c.PromoteAllResourceAttributes {
		if len(c.PromoteResourceAttributes) > 0 {
			return errors.New("'promote_all_resource_attributes' and 'promote_resource_attributes' cannot be configured simultaneously")
		}
		if err := sanitizeAttributes(c.IgnoreResourceAttributes, "ignored"); err != nil {
			return fmt.Errorf("invalid 'ignore_resource_attributes': %w", err)
		}
	} else {
		if len(c.IgnoreResourceAttributes) > 0 {
			return errors.New("'ignore_resource_attributes' cannot be configured unless 'promote_all_resource_attributes' is true")
		}
		if err := sanitizeAttributes(c.PromoteResourceAttributes, "promoted"); err != nil {