diff --git a/exporter/datadogexporter/config/config.go b/exporter/datadogexporter/config/config.go index a0b7bfcbeba6e..4a91c56f40c36 100644 --- a/exporter/datadogexporter/config/config.go +++ b/exporter/datadogexporter/config/config.go @@ -222,9 +222,15 @@ type MetricsExporterConfig struct { // resource attributes into metric labels, which are then converted into tags ResourceAttributesAsTags bool `mapstructure:"resource_attributes_as_tags"` + // Deprecated: Use InstrumentationScopeMetadataAsTags instead in favor of https://github.com/open-telemetry/opentelemetry-proto/releases/tag/v0.15.0 + // Both must not be enabled at the same time. // InstrumentationLibraryMetadataAsTags, if set to true, adds the name and version of the // instrumentation library that created a metric to the metric tags InstrumentationLibraryMetadataAsTags bool `mapstructure:"instrumentation_library_metadata_as_tags"` + + // InstrumentationScopeMetadataAsTags, if set to true, adds the name and version of the + // instrumentation scope that created a metric to the metric tags + InstrumentationScopeMetadataAsTags bool `mapstructure:"instrumentation_scope_metadata_as_tags"` } // TracesConfig defines the traces exporter specific configuration options diff --git a/exporter/datadogexporter/config/warn_envvars.go b/exporter/datadogexporter/config/warn_envvars.go index 76392741d751a..da16ea84bf51c 100644 --- a/exporter/datadogexporter/config/warn_envvars.go +++ b/exporter/datadogexporter/config/warn_envvars.go @@ -49,6 +49,7 @@ func futureDefaultConfig() *Config { ExporterConfig: MetricsExporterConfig{ ResourceAttributesAsTags: false, InstrumentationLibraryMetadataAsTags: false, + InstrumentationScopeMetadataAsTags: false, }, HistConfig: HistogramConfig{ Mode: "distributions", diff --git a/exporter/datadogexporter/example/config.yaml b/exporter/datadogexporter/example/config.yaml index cfbb98a7d588d..8e38d65f889ab 100644 --- a/exporter/datadogexporter/example/config.yaml +++ b/exporter/datadogexporter/example/config.yaml @@ -55,7 +55,7 @@ exporters: ## @params use_resource_metadata - boolean - optional - default: true ## Deprecated: [v0.49.0] Use `host_metadata::hostname_source` instead. ## This option will be removed in v0.52.0. - # + # # use_resource_metadata: true ## @param only_metadata - boolean - optional - default: false @@ -132,27 +132,35 @@ exporters: # # resource_attributes_as_tags: false + ## Deprecated: use instrumentation_scope_metadata_as_tags instead in favor of + ## https://github.com/open-telemetry/opentelemetry-proto/releases/tag/v0.15.0 + ## Both must not be enabled at the same time. ## @param instrumentation_library_metadata_as_tags - string - optional - default: false ## Set to true to add metadata about the instrumentation library that created a metric. # # instrumentation_library_metadata_as_tags: false + ## @param instrumentation_scope_metadata_as_tags - string - optional - default: false + ## Set to true to add metadata about the instrumentation scope that created a metric. + # + # instrumentation_scope_metadata_as_tags: false + ## @param histograms - custom object - optional ## Histograms specific configuration. ## @param mode - string - optional - default: distributions ## How to report histograms. Valid values are: - ## + ## ## - `distributions` to report metrics as Datadog distributions (recommended). ## - `nobuckets` to not report bucket metrics, ## - `counters` to report one metric per histogram bucket. # # mode: distributions - + ## @param send_count_sum_metrics - boolean - optional - default: false ## Whether to report sum and count as separate histogram metrics. # # send_count_sum_metrics: false - + ## @param sums - custom object - optional ## Sums specific configuration. ## @param cumulative_monotonic_mode - string - optional - default: to_delta @@ -167,7 +175,7 @@ exporters: ## Summaries specific configuration. ## @param mode - string - optional - default: gauges ## How to report summaries. Valid values are: - ## + ## ## - `noquantiles` to not report quantile metrics ## - `gauges` to report one gauge metric per quantile. # @@ -188,7 +196,7 @@ exporters: ## A blacklist of regular expressions can be provided to disable certain traces based on their resource name ## all entries must be surrounded by double quotes and separated by commas. # - # ignore_resources: ["(GET|POST) /healthcheck"] + # ignore_resources: ["(GET|POST) /healthcheck"] ## @param span_name_remappings - map of key/value pairs - optional ## A map of Datadog span operation name keys and preferred name valuues to update those names to. This can be used to @@ -196,7 +204,7 @@ exporters: ## shorten or modify span names to something more user friendly in the case of instrumentation libraries with ## particularly verbose names. # - # span_name_remappings: + # span_name_remappings: # io.opentelemetry.javaagent.spring.client: spring.client # instrumentation:express.server: express # go.opentelemetry.io_contrib_instrumentation_net_http_otelhttp.client: http.client @@ -207,7 +215,7 @@ exporters: ## https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/1909 # # span_name_as_resource_name: true - + ## @param host_metadata - custom object - optional ## Host metadata specific configuration. ## Host metadata is the information used for populating the infrastructure list, the host map and providing host tags functionality within the Datadog app. @@ -237,7 +245,7 @@ exporters: ## @param tags - list of strings - optional - default: empty list ## List of host tags to be sent as part of the host metadata. ## These tags will be attached to telemetry signals that have the host metadata hostname. - ## + ## ## To attach tags to telemetry signals regardless of the host, use a processor instead. # # tags: [] diff --git a/exporter/datadogexporter/example/example_k8s_manifest.yaml b/exporter/datadogexporter/example/example_k8s_manifest.yaml index 258803116b6ea..3a606b9e5d0e6 100644 --- a/exporter/datadogexporter/example/example_k8s_manifest.yaml +++ b/exporter/datadogexporter/example/example_k8s_manifest.yaml @@ -1,5 +1,5 @@ # This manifest file is meant as an example of how to deploy otel-agent as daemonset and otel-collector as a standalone service. -# Using this example should correctly identifies hostnames for individual k8s nodes. +# Using this example should correctly identifies hostnames for individual k8s nodes. # This is meant as an example only and may differ depending on deployment scenario and specifics of the environment the collector is used. --- # The k8sattributes processor may require additional diff --git a/exporter/datadogexporter/factory.go b/exporter/datadogexporter/factory.go index 711c46c0443fb..d045bbb173e3b 100644 --- a/exporter/datadogexporter/factory.go +++ b/exporter/datadogexporter/factory.go @@ -125,6 +125,7 @@ func (*factory) createDefaultConfig() config.Exporter { ExporterConfig: ddconfig.MetricsExporterConfig{ ResourceAttributesAsTags: false, InstrumentationLibraryMetadataAsTags: false, + InstrumentationScopeMetadataAsTags: false, }, HistConfig: ddconfig.HistogramConfig{ Mode: "distributions", diff --git a/exporter/datadogexporter/internal/model/internal/instrumentationscope/metadata.go b/exporter/datadogexporter/internal/model/internal/instrumentationscope/metadata.go new file mode 100644 index 0000000000000..767312c6a2c68 --- /dev/null +++ b/exporter/datadogexporter/internal/model/internal/instrumentationscope/metadata.go @@ -0,0 +1,34 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package instrumentationscope // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/internal/instrumentationscope" + +import ( + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/internal/utils" + "go.opentelemetry.io/collector/pdata/pcommon" +) + +const ( + instrumentationScopeTag = "instrumentation_scope" + instrumentationScopeVersionTag = "instrumentation_scope_version" +) + +// TagsFromInstrumentationScopeMetadata takes the name and version of +// the instrumentation scope and converts them to Datadog tags. +func TagsFromInstrumentationScopeMetadata(il pcommon.InstrumentationScope) []string { + return []string{ + utils.FormatKeyValueTag(instrumentationScopeTag, il.Name()), + utils.FormatKeyValueTag(instrumentationScopeVersionTag, il.Version()), + } +} diff --git a/exporter/datadogexporter/internal/model/internal/instrumentationscope/metadata_test.go b/exporter/datadogexporter/internal/model/internal/instrumentationscope/metadata_test.go new file mode 100644 index 0000000000000..7e6618ccfc736 --- /dev/null +++ b/exporter/datadogexporter/internal/model/internal/instrumentationscope/metadata_test.go @@ -0,0 +1,45 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package instrumentationscope + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" +) + +func TestTagsFromInstrumentationScopeMetadata(t *testing.T) { + tests := []struct { + name string + version string + expectedTags []string + }{ + {"test-il", "1.0.0", []string{fmt.Sprintf("%s:%s", instrumentationScopeTag, "test-il"), fmt.Sprintf("%s:%s", instrumentationScopeVersionTag, "1.0.0")}}, + {"test-il", "", []string{fmt.Sprintf("%s:%s", instrumentationScopeTag, "test-il"), fmt.Sprintf("%s:%s", instrumentationScopeVersionTag, "n/a")}}, + {"", "1.0.0", []string{fmt.Sprintf("%s:%s", instrumentationScopeTag, "n/a"), fmt.Sprintf("%s:%s", instrumentationScopeVersionTag, "1.0.0")}}, + {"", "", []string{fmt.Sprintf("%s:%s", instrumentationScopeTag, "n/a"), fmt.Sprintf("%s:%s", instrumentationScopeVersionTag, "n/a")}}, + } + + for _, testInstance := range tests { + il := pcommon.NewInstrumentationScope() + il.SetName(testInstance.name) + il.SetVersion(testInstance.version) + tags := TagsFromInstrumentationScopeMetadata(il) + + assert.ElementsMatch(t, testInstance.expectedTags, tags) + } +} diff --git a/exporter/datadogexporter/internal/model/translator/config.go b/exporter/datadogexporter/internal/model/translator/config.go index 40e03faf84d83..5246f28034085 100644 --- a/exporter/datadogexporter/internal/model/translator/config.go +++ b/exporter/datadogexporter/internal/model/translator/config.go @@ -18,12 +18,16 @@ import "fmt" type translatorConfig struct { // metrics export behavior - HistMode HistogramMode - SendCountSum bool - Quantiles bool - SendMonotonic bool - ResourceAttributesAsTags bool + HistMode HistogramMode + SendCountSum bool + Quantiles bool + SendMonotonic bool + ResourceAttributesAsTags bool + // Deprecated: Use InstrumentationScopeMetadataAsTags instead in favor of + // https://github.com/open-telemetry/opentelemetry-proto/releases/tag/v0.15.0 + // Both must not be enabled at the same time. InstrumentationLibraryMetadataAsTags bool + InstrumentationScopeMetadataAsTags bool // cache configuration sweepInterval int64 @@ -94,6 +98,14 @@ func WithInstrumentationLibraryMetadataAsTags() Option { } } +// WithInstrumentationScopeMetadataAsTags sets instrumentation scope metadata as tags. +func WithInstrumentationScopeMetadataAsTags() Option { + return func(t *translatorConfig) error { + t.InstrumentationScopeMetadataAsTags = true + return nil + } +} + // HistogramMode is an export mode for OTLP Histogram metrics. type HistogramMode string diff --git a/exporter/datadogexporter/internal/model/translator/metrics_translator.go b/exporter/datadogexporter/internal/model/translator/metrics_translator.go index 22bfa8061c420..740176804e46d 100644 --- a/exporter/datadogexporter/internal/model/translator/metrics_translator.go +++ b/exporter/datadogexporter/internal/model/translator/metrics_translator.go @@ -22,11 +22,11 @@ import ( "strconv" "github.com/DataDog/datadog-agent/pkg/quantile" - "go.opentelemetry.io/collector/pdata/pmetric" - "go.uber.org/zap" - "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/attributes" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/internal/instrumentationlibrary" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/internal/instrumentationscope" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.uber.org/zap" ) const metricName string = "metric name" @@ -47,6 +47,7 @@ func New(logger *zap.Logger, options ...Option) (*Translator, error) { SendMonotonic: true, ResourceAttributesAsTags: false, InstrumentationLibraryMetadataAsTags: false, + InstrumentationScopeMetadataAsTags: false, sweepInterval: 1800, deltaTTL: 3600, fallbackHostnameProvider: &noHostProvider{}, @@ -427,7 +428,9 @@ func (t *Translator) MapMetrics(ctx context.Context, md pmetric.Metrics, consume metricsArray := ilm.Metrics() var additionalTags []string - if t.cfg.InstrumentationLibraryMetadataAsTags { + if t.cfg.InstrumentationScopeMetadataAsTags { + additionalTags = append(attributeTags, instrumentationscope.TagsFromInstrumentationScopeMetadata(ilm.Scope())...) + } else if t.cfg.InstrumentationLibraryMetadataAsTags { additionalTags = append(attributeTags, instrumentationlibrary.TagsFromInstrumentationLibraryMetadata(ilm.Scope())...) } else { additionalTags = attributeTags diff --git a/exporter/datadogexporter/internal/model/translator/metrics_translator_test.go b/exporter/datadogexporter/internal/model/translator/metrics_translator_test.go index 80fbbfc2bb86e..3ea9946926e69 100644 --- a/exporter/datadogexporter/internal/model/translator/metrics_translator_test.go +++ b/exporter/datadogexporter/internal/model/translator/metrics_translator_test.go @@ -24,6 +24,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/quantile" "github.com/DataDog/datadog-agent/pkg/quantile/summary" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/attributes" gocache "github.com/patrickmn/go-cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -33,8 +34,6 @@ import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" - - "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/attributes" ) func TestIsCumulativeMonotonic(t *testing.T) { @@ -1215,26 +1214,30 @@ func TestMapMetrics(t *testing.T) { "env:dev", } - ilName := "instrumentation_library" - ilVersion := "1.0.0" + instructionName := "foo" + instructionVersion := "1.0.0" ilTags := []string{ - fmt.Sprintf("instrumentation_library:%s", ilName), - fmt.Sprintf("instrumentation_library_version:%s", ilVersion), + fmt.Sprintf("instrumentation_library:%s", instructionName), + fmt.Sprintf("instrumentation_library_version:%s", instructionVersion), + } + isTags := []string{ + fmt.Sprintf("instrumentation_scope:%s", instructionName), + fmt.Sprintf("instrumentation_scope_version:%s", instructionVersion), } tests := []struct { - name string resourceAttributesAsTags bool instrumentationLibraryMetadataAsTags bool + instrumentationScopeMetadataAsTags bool expectedMetrics []metric expectedSketches []sketch expectedUnknownMetricType int expectedUnsupportedAggregationTemporality int }{ { - name: "ResourceAttributesAsTags: false, InstrumentationLibraryMetadataAsTags: false", resourceAttributesAsTags: false, instrumentationLibraryMetadataAsTags: false, + instrumentationScopeMetadataAsTags: false, expectedMetrics: []metric{ newGaugeWithHostname("int.gauge", 1, attrTags), newGaugeWithHostname("double.gauge", math.Pi, attrTags), @@ -1271,9 +1274,9 @@ func TestMapMetrics(t *testing.T) { expectedUnsupportedAggregationTemporality: 3, }, { - name: "ResourceAttributesAsTags: true, InstrumentationLibraryMetadataAsTags: false", resourceAttributesAsTags: true, instrumentationLibraryMetadataAsTags: false, + instrumentationScopeMetadataAsTags: false, expectedMetrics: []metric{ newGaugeWithHostname("int.gauge", 1, attrTags), newGaugeWithHostname("double.gauge", math.Pi, attrTags), @@ -1310,9 +1313,9 @@ func TestMapMetrics(t *testing.T) { expectedUnsupportedAggregationTemporality: 3, }, { - name: "ResourceAttributesAsTags: false, InstrumentationLibraryMetadataAsTags: true", resourceAttributesAsTags: false, instrumentationLibraryMetadataAsTags: true, + instrumentationScopeMetadataAsTags: false, expectedMetrics: []metric{ newGaugeWithHostname("int.gauge", 1, append(attrTags, ilTags...)), newGaugeWithHostname("double.gauge", math.Pi, append(attrTags, ilTags...)), @@ -1349,9 +1352,9 @@ func TestMapMetrics(t *testing.T) { expectedUnsupportedAggregationTemporality: 3, }, { - name: "ResourceAttributesAsTags: true, InstrumentationLibraryMetadataAsTags: true", resourceAttributesAsTags: true, instrumentationLibraryMetadataAsTags: true, + instrumentationScopeMetadataAsTags: false, expectedMetrics: []metric{ newGaugeWithHostname("int.gauge", 1, append(attrTags, ilTags...)), newGaugeWithHostname("double.gauge", math.Pi, append(attrTags, ilTags...)), @@ -1387,11 +1390,56 @@ func TestMapMetrics(t *testing.T) { expectedUnknownMetricType: 1, expectedUnsupportedAggregationTemporality: 3, }, + { + resourceAttributesAsTags: true, + instrumentationLibraryMetadataAsTags: false, + instrumentationScopeMetadataAsTags: true, + expectedMetrics: []metric{ + newGaugeWithHostname("int.gauge", 1, append(attrTags, isTags...)), + newGaugeWithHostname("double.gauge", math.Pi, append(attrTags, isTags...)), + newCountWithHostname("int.delta.sum", 2, 0, append(attrTags, isTags...)), + newCountWithHostname("double.delta.sum", math.E, 0, append(attrTags, isTags...)), + newCountWithHostname("int.delta.monotonic.sum", 2, 0, append(attrTags, isTags...)), + newCountWithHostname("double.delta.monotonic.sum", math.E, 0, append(attrTags, isTags...)), + newCountWithHostname("summary.sum", 10_000, 2, append(attrTags, isTags...)), + newCountWithHostname("summary.count", 100, 2, append(attrTags, isTags...)), + newGaugeWithHostname("int.cumulative.sum", 4, append(attrTags, isTags...)), + newGaugeWithHostname("double.cumulative.sum", 4, append(attrTags, isTags...)), + newCountWithHostname("int.cumulative.monotonic.sum", 3, 2, append(attrTags, isTags...)), + newCountWithHostname("double.cumulative.monotonic.sum", math.Pi, 2, append(attrTags, isTags...)), + }, + expectedSketches: []sketch{ + newSketchWithHostname("double.histogram", summary.Summary{ + Min: 0, + Max: 0, + Sum: math.Phi, + Avg: math.Phi / 20, + Cnt: 20, + }, append(attrTags, isTags...)), + newSketchWithHostname("double.exponentialHistogram", summary.Summary{ + // Expected min: lower bound of the highest negative bucket + Min: -math.Pow(math.Pow(2, math.Pow(2, -6)), 7), + // Expected max: upper bound of the highest positive bucket + Max: math.Pow(math.Pow(2, math.Pow(2, -6)), 5), + Sum: math.Phi, + Avg: math.Phi / 25, + Cnt: 25, + }, append(attrTags, isTags...)), + }, + expectedUnknownMetricType: 1, + expectedUnsupportedAggregationTemporality: 3, + }, } for _, testInstance := range tests { - t.Run(testInstance.name, func(t *testing.T) { - md := createTestMetrics(attrs, ilName, ilVersion) + name := fmt.Sprintf( + "ResourceAttributesAsTags: %t, InstrumentationScopeMetadataAsTags: %t, InstrumentationLibraryName: %t", + testInstance.resourceAttributesAsTags, + testInstance.instrumentationScopeMetadataAsTags, + testInstance.instrumentationLibraryMetadataAsTags, + ) + t.Run(name, func(t *testing.T) { + md := createTestMetrics(attrs, instructionName, instructionVersion) core, observed := observer.New(zapcore.DebugLevel) testLogger := zap.New(core) @@ -1402,6 +1450,9 @@ func TestMapMetrics(t *testing.T) { if testInstance.resourceAttributesAsTags { options = append(options, WithResourceAttributesAsTags()) } + if testInstance.instrumentationScopeMetadataAsTags { + options = append(options, WithInstrumentationScopeMetadataAsTags()) + } if testInstance.instrumentationLibraryMetadataAsTags { options = append(options, WithInstrumentationLibraryMetadataAsTags()) } diff --git a/exporter/datadogexporter/metrics_exporter.go b/exporter/datadogexporter/metrics_exporter.go index 2e7b08dfd4f27..86d3f3a41c2ea 100644 --- a/exporter/datadogexporter/metrics_exporter.go +++ b/exporter/datadogexporter/metrics_exporter.go @@ -72,6 +72,14 @@ func translatorFromConfig(logger *zap.Logger, cfg *config.Config, hostProvider p options = append(options, translator.WithResourceAttributesAsTags()) } + if cfg.Metrics.ExporterConfig.InstrumentationScopeMetadataAsTags && cfg.Metrics.ExporterConfig.InstrumentationLibraryMetadataAsTags { + return nil, fmt.Errorf("cannot use both instrumentation_library_metadata_as_tags(deprecated) and instrumentation_scope_metadata_as_tags") + } + + if cfg.Metrics.ExporterConfig.InstrumentationScopeMetadataAsTags { + options = append(options, translator.WithInstrumentationScopeMetadataAsTags()) + } + if cfg.Metrics.ExporterConfig.InstrumentationLibraryMetadataAsTags { options = append(options, translator.WithInstrumentationLibraryMetadataAsTags()) }