Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ processors:
allow_hostname_override: false
cardinality: 0
logs_tags_as_ddtags: false
metrics_attributes_as_tags: false
trace_container_tag_promotion: "off"
receivers:
otlp:
Expand Down
5 changes: 5 additions & 0 deletions comp/otelcol/otlp/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ type PipelineConfig struct {
// `ddtags` log record attribute (real Datadog log tags) instead of resource
// attributes (log attributes) for the Logs pipeline.
LogsTagsAsDDTags bool
// MetricsInfraAttrsAsTags controls whether the InfraAttributes processor promotes
// custom tags (e.g. from kubernetesResourcesLabelsAsTags/AnnotationsAsTags) so they
// survive the metrics translator's allowlist and become metric tags for the Metrics
// pipeline. Without it, custom tags that are not known DD/OTel conventions are dropped.
MetricsInfraAttrsAsTags bool
// Logs contains configuration options for the logs
Logs map[string]interface{}
// Debug contains debug configurations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@ type Config struct {
//
// This only affects the logs pipeline.
LogsTagsAsDDTags bool `mapstructure:"logs_tags_as_ddtags"`

// MetricsAttributesAsTags controls whether custom tags emitted by the tagger
// (e.g. via kubernetesResourcesLabelsAsTags / AnnotationsAsTags) are promoted
// so they survive the metrics translator's allowlist and become metric tags.
//
// The metrics translator (attributes.TagsFromAttributes) keeps only an
// allowlist of known DD / OTel convention keys and drops arbitrary keys. To
// get custom tags onto metrics, the processor writes them under the
// `datadog.container.tag.<key>` prefix that the translator promotes into metric
// tags (attributes.ContainerTagsFromResourceAttributes). When false (default),
// behavior is unchanged: custom tags remain plain resource attributes and are
// dropped by the allowlist.
//
// This only affects the metrics pipeline.
MetricsAttributesAsTags bool `mapstructure:"metrics_attributes_as_tags"`
}

var _ component.Config = (*Config)(nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ properties:
logs_tags_as_ddtags:
description: 'LogsTagsAsDDTags controls whether custom tags emitted by the tagger (e.g. via kubernetesResourcesLabelsAsTags / AnnotationsAsTags) are written as a `ddtags` log record attribute -- which the Datadog logs intake turns into real log tags -- instead of as resource attributes, which surface as log attributes.'
type: boolean
metrics_attributes_as_tags:
description: 'MetricsAttributesAsTags controls whether custom tags emitted by the tagger (e.g. via kubernetesResourcesLabelsAsTags / AnnotationsAsTags) are promoted so they survive the metrics translator''s allowlist and become metric tags.'
type: boolean
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,24 @@ func newInfraAttributesMetricProcessor(
}

func (iamp *infraAttributesMetricProcessor) processMetrics(_ context.Context, md pmetric.Metrics) (pmetric.Metrics, error) {
// When metrics_attributes_as_tags is enabled, promote custom tagger labels (e.g. from
// kubernetesResourcesLabelsAsTags) so they survive the metrics translator's
// allowlist. The metrics path consumes them via the `datadog.container.tag.`
// prefix: attributes.TagsFromAttributes (metrics_translator.go) calls
// attributes.ContainerTagsFromResourceAttributes, which extracts that prefix
// into metric tags. Without promotion, custom labels that are not known DD /
// OTel conventions are dropped from OTLP metrics (see OTELS-1131). "duplicate"
// keeps the original resource attribute and additionally writes the prefixed
// form the translator reads.
promote := ContainerTagPromotionOff
if iamp.cfg.MetricsAttributesAsTags {
promote = ContainerTagPromotionDuplicate
}

rms := md.ResourceMetrics()
for i := 0; i < rms.Len(); i++ {
resourceAttributes := rms.At(i).Resource().Attributes()
// trace_container_tag_promotion only makes sense for traces: it exists to
// feed trace-agent's `_dd.tags.container` promotion
// (ConsumeContainerTagsFromResource), which metrics never go through.
// The metrics path already recognizes DD-format keys directly via
// kubernetesDDTags, so prefixing would only strand data under
// `rename`. Always pass "off" here regardless of the configured mode.
iamp.infraTags.ProcessTags(iamp.logger, iamp.cardinality, resourceAttributes, iamp.cfg.AllowHostnameOverride, ContainerTagPromotionOff, false, nil)
iamp.infraTags.ProcessTags(iamp.logger, iamp.cardinality, resourceAttributes, iamp.cfg.AllowHostnameOverride, promote, false, nil)
}
return md, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,71 @@ func TestInfraAttributesMetricProcessorIgnoresContainerTagPromotion(t *testing.T
}
}

// TestInfraAttributesMetricProcessorMetricsAttributesAsTags verifies that a custom
// tagger tag is promoted under the `datadog.container.tag.` prefix (so the
// metrics translator keeps it as a metric tag) only when metrics_attributes_as_tags
// is enabled. This is the OTELS-1131 fix.
func TestInfraAttributesMetricProcessorMetricsAttributesAsTags(t *testing.T) {
tests := []struct {
name string
attributesAsTags bool
expected map[string]any
}{
{
name: "disabled: custom tag dropped by translator (stays unprefixed)",
attributesAsTags: false,
expected: map[string]any{
"container.id": "test",
"test_tag": "bar",
},
},
{
name: "enabled: custom tag duplicated under prefixed key",
attributesAsTags: true,
expected: map[string]any{
"container.id": "test",
"test_tag": "bar",
"datadog.container.tag.test_tag": "bar",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
next := new(consumertest.MetricsSink)
cfg := &Config{
Cardinality: types.LowCardinality,
MetricsAttributesAsTags: tt.attributesAsTags,
}
tc := testutil.NewTestTaggerClient()
tc.TagMap["container_id://test"] = []string{"test_tag:bar"}

factory := NewFactoryForAgent(tc, func(_ context.Context) (string, error) {
return "test-host", nil
})
fmp, err := factory.CreateMetrics(
context.Background(),
processortest.NewNopSettings(Type),
cfg,
next,
)
assert.NoError(t, err)
ctx := context.Background()
assert.NoError(t, fmp.Start(ctx, nil))

md := testResourceMetrics([]metricWithResource{{
metricNames: inMetricNames,
resourceAttributes: map[string]any{"container.id": "test"},
}})
assert.NoError(t, fmp.ConsumeMetrics(ctx, md))
assert.NoError(t, fmp.Shutdown(ctx))

assert.Len(t, next.AllMetrics(), 1)
out := next.AllMetrics()[0].ResourceMetrics().At(0).Resource().Attributes().AsRaw()
Comment on lines +336 to +337

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only verifies the processor’s resource attributes; it never exercises the metrics translator or confirms that test_tag:bar reaches intake. Add a new-e2e assertion against fakeintake, since this change specifically alters emitted metric tags and unit coverage cannot catch failures in the downstream translation/serialization path.

assert.EqualValues(t, tt.expected, out)
})
}
}

func TestEntityIDsFromAttributes(t *testing.T) {
tests := []struct {
name string
Expand Down
2 changes: 2 additions & 0 deletions comp/otelcol/otlp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func FromAgentConfig(cfg config.Reader) (PipelineConfig, error) {
TracesInfraAttributesEnabled := cfg.GetBool(coreconfig.OTLPTracesInfraAttrEnabled)
tracesContainerTagPromotion := cfg.GetString(coreconfig.OTLPTracesInfraAttrContainerTagPromotion)
logsTagsAsDDTags := cfg.GetBool(coreconfig.OTLPLogsInfraAttrTagsAsDDTags)
metricsInfraAttrsAsTags := cfg.GetBool(coreconfig.OTLPMetricsInfraAttrAsTags)

if !metricsEnabled && !tracesEnabled && !logsEnabled {
errs = append(errs, errors.New("at least one OTLP signal needs to be enabled"))
Expand Down Expand Up @@ -129,6 +130,7 @@ func FromAgentConfig(cfg config.Reader) (PipelineConfig, error) {
TracesInfraAttributesEnabled: TracesInfraAttributesEnabled,
TracesContainerTagPromotion: tracesContainerTagPromotion,
LogsTagsAsDDTags: logsTagsAsDDTags,
MetricsInfraAttrsAsTags: metricsInfraAttrsAsTags,
MetricsBatch: metricsBatchConfig.ToStringMap(),
Logs: logsConfig.ToStringMap(),
Debug: debugMap,
Expand Down
6 changes: 6 additions & 0 deletions comp/otelcol/otlp/map_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ func buildMetricsMap(cfg PipelineConfig) (*confmap.Conf, error) {
buildKey("exporters", "serializer", "metrics"): cfg.Metrics,
buildKey("exporters", "serializer", "sending_queue", "batch"): ensureNonNilMap(cfg.MetricsBatch),
}
// The metrics pipeline shares the `infraattributes` processor instance with
// the logs pipeline (see defaultMetricsConfig/defaultLogsConfig); this is
// harmless since the logs processor ignores metrics_attributes_as_tags.
if cfg.MetricsInfraAttrsAsTags {
smap[buildKey("processors", "infraattributes", "metrics_attributes_as_tags")] = true
}
{
configMap := confmap.NewFromStringMap(smap)
err = baseMap.Merge(configMap)
Expand Down
63 changes: 63 additions & 0 deletions comp/otelcol/otlp/map_provider_not_serverless_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,69 @@ func TestNewMap(t *testing.T) {
},
},
},
{
name: "only metrics, metrics_attributes_as_tags on",
pcfg: PipelineConfig{
OTLPReceiverConfig: testutil.OTLPConfigFromPorts("bindhost", 0, 1234),
TracePort: 5003,
MetricsEnabled: true,
TracesInfraAttributesEnabled: true,
TracesContainerTagPromotion: "off",
MetricsInfraAttrsAsTags: true,
Metrics: map[string]any{
"delta_ttl": 1500,
"resource_attributes_as_tags": false,
"instrumentation_scope_metadata_as_tags": false,
"histograms": map[string]any{
"mode": "nobuckets",
"send_count_sum_metrics": true,
},
},
Debug: map[string]any{
"verbosity": "none",
},
},
ocfg: map[string]any{
"receivers": map[string]any{
"otlp": map[string]any{
"protocols": map[string]any{
"http": map[string]any{
"endpoint": "bindhost:1234",
},
},
},
},
"processors": map[string]any{
"infraattributes": map[string]any{"metrics_attributes_as_tags": true},
},
"exporters": map[string]any{
"serializer": map[string]any{
"metrics": map[string]any{
"delta_ttl": 1500,
"resource_attributes_as_tags": false,
"instrumentation_scope_metadata_as_tags": false,
"histograms": map[string]any{
"mode": "nobuckets",
"send_count_sum_metrics": true,
},
},
"sending_queue": map[string]any{
"batch": map[string]any{},
},
},
},
"service": map[string]any{
"telemetry": map[string]any{"metrics": map[string]any{"level": "none"}},
"pipelines": map[string]any{
"metrics": map[string]any{
"receivers": []any{"otlp"},
"processors": []any{"infraattributes"},
"exporters": []any{"serializer"},
},
},
},
},
},
{
name: "only gRPC, only Traces, logging with normal verbosity",
pcfg: PipelineConfig{
Expand Down
23 changes: 23 additions & 0 deletions pkg/config/schema/yaml/core_schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4500,6 +4500,29 @@ properties:
description: |-
Set to false to disable metrics support in the OTLP ingest endpoint.
To enable the OTLP ingest, the otlp_config.receiver section must be set.
infra_attributes:
node_type: section
type: object
visibility: public
description: Controls whether infrastructure attributes (host tags, container
tags) are attached to OTLP metrics ingested by the Agent.
tags:
- template_section:CoreAgent
properties:
as_tags:
node_type: setting
type: boolean
default: false
visibility: public
description: |-
Controls whether the Infra-Attribute-Processor promotes custom tags (for example, from
kubernetesResourcesLabelsAsTags/AnnotationsAsTags) so they survive the metrics translator's
allowlist and are emitted as metric tags. Without it, custom tags that are not known Datadog
or OpenTelemetry conventions are dropped.
comment: |-
as_tags controls whether the infraattributes processor promotes custom tags
(e.g. from kubernetesResourcesLabelsAsTags/AnnotationsAsTags) so they survive the metrics
translator's allowlist and become metric tags.
resource_attributes_as_tags:
node_type: setting
type: boolean
Expand Down
7 changes: 4 additions & 3 deletions pkg/config/setup/otlp.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ const (
OTLPReceiverSubSectionKey = "receiver"
OTLPReceiverSection = OTLPSection + "." + OTLPReceiverSubSectionKey

OTLPMetrics = OTLPSection + ".metrics"
OTLPMetricsEnabled = OTLPMetrics + ".enabled"
OTLPMetricsBatch = OTLPMetrics + ".batch"
OTLPMetrics = OTLPSection + ".metrics"
OTLPMetricsEnabled = OTLPMetrics + ".enabled"
OTLPMetricsBatch = OTLPMetrics + ".batch"
OTLPMetricsInfraAttrAsTags = OTLPMetrics + ".infra_attributes.as_tags"

OTLPDebug = OTLPSection + "." + "debug"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Each section from every release note are combined when the
# CHANGELOG.rst is rendered. So the text needs to be worded so that
# it does not depend on any information only available in another
# section. This may mean repeating some details, but each section
# must be readable independently of the other.
#
# Each section note must be formatted as reStructuredText.
---
features:
- |
OTLP ingestion: Adds a new ``otlp_config.metrics.infra_attributes.as_tags`` option. When
enabled, custom tagger-derived tags (for example, tags configured via
``kubernetesResourcesLabelsAsTags``/``kubernetesResourcesAnnotationsAsTags``) are promoted so they
survive the metrics translator's allowlist and are emitted as metric tags for OTLP metrics ingested
directly by the Agent. Without it, custom tags that are not known Datadog or OpenTelemetry
conventions are dropped. Default behavior is unchanged.
- |
DDOT: The ``infraattributes`` processor now supports a new ``metrics_attributes_as_tags`` option for the
metrics pipeline. When enabled, custom tagger-derived tags (for example, tags configured via
``kubernetesResourcesLabelsAsTags``/``kubernetesResourcesAnnotationsAsTags``) are promoted so they
survive the metrics translator's allowlist and are emitted as metric tags. Default behavior is
unchanged.
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ processors:
allow_hostname_override: false
cardinality: 0
logs_tags_as_ddtags: false
metrics_attributes_as_tags: false
trace_container_tag_promotion: "off"
filter/drop-prometheus-internal-metrics/dd-autoconfigured:
error_mode: ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ processors:
allow_hostname_override: false
cardinality: 0
logs_tags_as_ddtags: false
metrics_attributes_as_tags: false
trace_container_tag_promotion: "off"
filter/drop-prometheus-internal-metrics/dd-autoconfigured:
error_mode: ignore
Expand Down
Loading