diff --git a/.chloggen/mongodbreceiver-duplicate-attribute-24972.yaml b/.chloggen/mongodbreceiver-duplicate-attribute-24972.yaml index 1e217ac1e884..6e6807665455 100755 --- a/.chloggen/mongodbreceiver-duplicate-attribute-24972.yaml +++ b/.chloggen/mongodbreceiver-duplicate-attribute-24972.yaml @@ -7,7 +7,7 @@ change_type: 'bug_fix' component: mongodbreceiver # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). -note: Remove duplicate database name attribute, database name reported as a datapoint attribute and as a resource attribute, one of them should be removed +note: "add `receiver.mongodb.removeDatabaseResourceAttr` Alpha feature gate to remove duplicate database name attribute" # Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. issues: [24972] diff --git a/receiver/mongodbreceiver/README.md b/receiver/mongodbreceiver/README.md index b8cf83199cb1..70940732a0fa 100644 --- a/receiver/mongodbreceiver/README.md +++ b/receiver/mongodbreceiver/README.md @@ -76,3 +76,15 @@ The following metric are available with versions: - `mongodb.index.access.count` >= 4.0 Details about the metrics produced by this receiver can be found in [metadata.yaml](./metadata.yaml) + +## Feature gate configurations +See the [Collector feature gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md#collector-feature-gates) for an overview of feature gates in the collector. + +**ALPHA**: `receiver.mongodb.removeDatabaseResourceAttr` + +The feature gate `receiver.mongodb.removeDatabaseResourceAttr` once enabled will remove database name resource attribute, +because both resource and datapoint attributes are called database. + +This feature gate will eventually be enabled by default, and eventually the old implementation will be removed. It aims +to give users time to migrate to the new implementation. The target release for this featuregate to be enabled by default +is 0.93.0. diff --git a/receiver/mongodbreceiver/documentation.md b/receiver/mongodbreceiver/documentation.md index ddda0c7aa750..095d3859e430 100644 --- a/receiver/mongodbreceiver/documentation.md +++ b/receiver/mongodbreceiver/documentation.md @@ -397,3 +397,9 @@ The amount of time that the server has been running. | Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | | ---- | ----------- | ---------- | ----------------------- | --------- | | ms | Sum | Int | Cumulative | true | + +## Resource Attributes + +| Name | Description | Values | Enabled | +| ---- | ----------- | ------ | ------- | +| database | The name of a database. | Any Str | false | diff --git a/receiver/mongodbreceiver/internal/metadata/generated_config.go b/receiver/mongodbreceiver/internal/metadata/generated_config.go index 760ee309bc59..471ddcb55a41 100644 --- a/receiver/mongodbreceiver/internal/metadata/generated_config.go +++ b/receiver/mongodbreceiver/internal/metadata/generated_config.go @@ -152,13 +152,47 @@ func DefaultMetricsConfig() MetricsConfig { } } +// ResourceAttributeConfig provides common config for a particular resource attribute. +type ResourceAttributeConfig struct { + Enabled bool `mapstructure:"enabled"` + + enabledSetByUser bool +} + +func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error { + if parser == nil { + return nil + } + err := parser.Unmarshal(rac, confmap.WithErrorUnused()) + if err != nil { + return err + } + rac.enabledSetByUser = parser.IsSet("enabled") + return nil +} + +// ResourceAttributesConfig provides config for mongodb resource attributes. +type ResourceAttributesConfig struct { + Database ResourceAttributeConfig `mapstructure:"database"` +} + +func DefaultResourceAttributesConfig() ResourceAttributesConfig { + return ResourceAttributesConfig{ + Database: ResourceAttributeConfig{ + Enabled: false, + }, + } +} + // MetricsBuilderConfig is a configuration for mongodb metrics builder. type MetricsBuilderConfig struct { - Metrics MetricsConfig `mapstructure:"metrics"` + Metrics MetricsConfig `mapstructure:"metrics"` + ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"` } func DefaultMetricsBuilderConfig() MetricsBuilderConfig { return MetricsBuilderConfig{ - Metrics: DefaultMetricsConfig(), + Metrics: DefaultMetricsConfig(), + ResourceAttributes: DefaultResourceAttributesConfig(), } } diff --git a/receiver/mongodbreceiver/internal/metadata/generated_config_test.go b/receiver/mongodbreceiver/internal/metadata/generated_config_test.go index 4cc9eec8db00..34192fe04db0 100644 --- a/receiver/mongodbreceiver/internal/metadata/generated_config_test.go +++ b/receiver/mongodbreceiver/internal/metadata/generated_config_test.go @@ -57,6 +57,9 @@ func TestMetricsBuilderConfig(t *testing.T) { MongodbStorageSize: MetricConfig{Enabled: true}, MongodbUptime: MetricConfig{Enabled: true}, }, + ResourceAttributes: ResourceAttributesConfig{ + Database: ResourceAttributeConfig{Enabled: true}, + }, }, }, { @@ -94,13 +97,16 @@ func TestMetricsBuilderConfig(t *testing.T) { MongodbStorageSize: MetricConfig{Enabled: false}, MongodbUptime: MetricConfig{Enabled: false}, }, + ResourceAttributes: ResourceAttributesConfig{ + Database: ResourceAttributeConfig{Enabled: false}, + }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cfg := loadMetricsBuilderConfig(t, tt.name) - if diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(MetricConfig{})); diff != "" { + if diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(MetricConfig{}, ResourceAttributeConfig{})); diff != "" { t.Errorf("Config mismatch (-expected +actual):\n%s", diff) } }) @@ -116,3 +122,47 @@ func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig { require.NoError(t, component.UnmarshalConfig(sub, &cfg)) return cfg } + +func TestResourceAttributesConfig(t *testing.T) { + tests := []struct { + name string + want ResourceAttributesConfig + }{ + { + name: "default", + want: DefaultResourceAttributesConfig(), + }, + { + name: "all_set", + want: ResourceAttributesConfig{ + Database: ResourceAttributeConfig{Enabled: true}, + }, + }, + { + name: "none_set", + want: ResourceAttributesConfig{ + Database: ResourceAttributeConfig{Enabled: false}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := loadResourceAttributesConfig(t, tt.name) + if diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{})); diff != "" { + t.Errorf("Config mismatch (-expected +actual):\n%s", diff) + } + }) + } +} + +func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + sub, err := cm.Sub(name) + require.NoError(t, err) + sub, err = sub.Sub("resource_attributes") + require.NoError(t, err) + cfg := DefaultResourceAttributesConfig() + require.NoError(t, component.UnmarshalConfig(sub, &cfg)) + return cfg +} diff --git a/receiver/mongodbreceiver/internal/metadata/generated_metrics.go b/receiver/mongodbreceiver/internal/metadata/generated_metrics.go index 10f24ae58745..041a82b905bd 100644 --- a/receiver/mongodbreceiver/internal/metadata/generated_metrics.go +++ b/receiver/mongodbreceiver/internal/metadata/generated_metrics.go @@ -1878,6 +1878,9 @@ func WithStartTime(startTime pcommon.Timestamp) metricBuilderOption { } func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.CreateSettings, options ...metricBuilderOption) *MetricsBuilder { + if mbc.ResourceAttributes.Database.enabledSetByUser { + settings.Logger.Warn("[WARNING] `database` should not be configured: This resource_attribute is deprecated and will be removed soon.") + } mb := &MetricsBuilder{ config: mbc, startTime: pcommon.NewTimestampFromTime(time.Now()), @@ -1920,6 +1923,11 @@ func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.CreateSetting return mb } +// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics. +func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder { + return NewResourceBuilder(mb.config.ResourceAttributes) +} + // updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity. func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) { if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() { diff --git a/receiver/mongodbreceiver/internal/metadata/generated_metrics_test.go b/receiver/mongodbreceiver/internal/metadata/generated_metrics_test.go index 688ed96aca17..b7c10f8108ad 100644 --- a/receiver/mongodbreceiver/internal/metadata/generated_metrics_test.go +++ b/receiver/mongodbreceiver/internal/metadata/generated_metrics_test.go @@ -49,6 +49,10 @@ func TestMetricsBuilder(t *testing.T) { mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, test.name), settings, WithStartTime(start)) expectedWarnings := 0 + if test.configSet == testSetAll || test.configSet == testSetNone { + assert.Equal(t, "[WARNING] `database` should not be configured: This resource_attribute is deprecated and will be removed soon.", observedLogs.All()[expectedWarnings].Message) + expectedWarnings++ + } assert.Equal(t, expectedWarnings, observedLogs.Len()) @@ -167,7 +171,9 @@ func TestMetricsBuilder(t *testing.T) { allMetricsCount++ mb.RecordMongodbUptimeDataPoint(ts, 1) - res := pcommon.NewResource() + rb := mb.NewResourceBuilder() + rb.SetDatabase("database-val") + res := rb.Emit() metrics := mb.Emit(WithResource(res)) if test.configSet == testSetNone { diff --git a/receiver/mongodbreceiver/internal/metadata/generated_resource.go b/receiver/mongodbreceiver/internal/metadata/generated_resource.go new file mode 100644 index 000000000000..7c72b171f2db --- /dev/null +++ b/receiver/mongodbreceiver/internal/metadata/generated_resource.go @@ -0,0 +1,36 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/pdata/pcommon" +) + +// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml. +// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines. +type ResourceBuilder struct { + config ResourceAttributesConfig + res pcommon.Resource +} + +// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application. +func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder { + return &ResourceBuilder{ + config: rac, + res: pcommon.NewResource(), + } +} + +// SetDatabase sets provided value as "database" attribute. +func (rb *ResourceBuilder) SetDatabase(val string) { + if rb.config.Database.Enabled { + rb.res.Attributes().PutStr("database", val) + } +} + +// Emit returns the built resource and resets the internal builder state. +func (rb *ResourceBuilder) Emit() pcommon.Resource { + r := rb.res + rb.res = pcommon.NewResource() + return r +} diff --git a/receiver/mongodbreceiver/internal/metadata/generated_resource_test.go b/receiver/mongodbreceiver/internal/metadata/generated_resource_test.go new file mode 100644 index 000000000000..666d88d5df3e --- /dev/null +++ b/receiver/mongodbreceiver/internal/metadata/generated_resource_test.go @@ -0,0 +1,40 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResourceBuilder(t *testing.T) { + for _, test := range []string{"default", "all_set", "none_set"} { + t.Run(test, func(t *testing.T) { + cfg := loadResourceAttributesConfig(t, test) + rb := NewResourceBuilder(cfg) + rb.SetDatabase("database-val") + + res := rb.Emit() + assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource + + switch test { + case "default": + assert.Equal(t, 0, res.Attributes().Len()) + case "all_set": + assert.Equal(t, 1, res.Attributes().Len()) + case "none_set": + assert.Equal(t, 0, res.Attributes().Len()) + return + default: + assert.Failf(t, "unexpected test case: %s", test) + } + + val, ok := res.Attributes().Get("database") + assert.Equal(t, test == "all_set", ok) + if ok { + assert.EqualValues(t, "database-val", val.Str()) + } + }) + } +} diff --git a/receiver/mongodbreceiver/internal/metadata/testdata/config.yaml b/receiver/mongodbreceiver/internal/metadata/testdata/config.yaml index 59e9ae07405e..41abdfb07ca2 100644 --- a/receiver/mongodbreceiver/internal/metadata/testdata/config.yaml +++ b/receiver/mongodbreceiver/internal/metadata/testdata/config.yaml @@ -61,6 +61,9 @@ all_set: enabled: true mongodb.uptime: enabled: true + resource_attributes: + database: + enabled: true none_set: metrics: mongodb.cache.operations: @@ -123,3 +126,6 @@ none_set: enabled: false mongodb.uptime: enabled: false + resource_attributes: + database: + enabled: false diff --git a/receiver/mongodbreceiver/metadata.yaml b/receiver/mongodbreceiver/metadata.yaml index 1d8286c2614c..32514a9477cf 100644 --- a/receiver/mongodbreceiver/metadata.yaml +++ b/receiver/mongodbreceiver/metadata.yaml @@ -8,6 +8,14 @@ status: codeowners: active: [djaglowski, schmikei] +resource_attributes: + database: + description: The name of a database. + enabled: false + type: string + warnings: + if_configured: This resource_attribute is deprecated and will be removed soon. + attributes: database: description: The name of a database. diff --git a/receiver/mongodbreceiver/scraper.go b/receiver/mongodbreceiver/scraper.go index 12615755585e..74c1a32c9a3d 100644 --- a/receiver/mongodbreceiver/scraper.go +++ b/receiver/mongodbreceiver/scraper.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/go-version" "go.mongodb.org/mongo-driver/bson" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/featuregate" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.opentelemetry.io/collector/receiver" @@ -21,7 +22,21 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/mongodbreceiver/internal/metadata" ) -var unknownVersion = func() *version.Version { return version.Must(version.NewVersion("0.0")) } +const ( + readmeURL = "https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/mongodbreceiver/README.md" + removeDatabaseResourceAttrID = "receiver.mongodb.removeDatabaseResourceAttr" +) + +var ( + unknownVersion = func() *version.Version { return version.Must(version.NewVersion("0.0")) } + + removeDatabaseResourceAttrFeatureGate = featuregate.GlobalRegistry().MustRegister( + removeDatabaseResourceAttrID, + featuregate.StageAlpha, + featuregate.WithRegisterDescription("Remove duplicate database name resource attribute"), + featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/24972"), + featuregate.WithRegisterFromVersion("v0.88.0")) +) type mongodbScraper struct { logger *zap.Logger @@ -29,15 +44,27 @@ type mongodbScraper struct { client client mongoVersion *version.Version mb *metadata.MetricsBuilder + + // removeDatabaseResourceAttr if enabled, will remove database resource attribute on database metrics + removeDatabaseResourceAttr bool } func newMongodbScraper(settings receiver.CreateSettings, config *Config) *mongodbScraper { - return &mongodbScraper{ - logger: settings.Logger, - config: config, - mb: metadata.NewMetricsBuilder(config.MetricsBuilderConfig, settings), - mongoVersion: unknownVersion(), + ms := &mongodbScraper{ + logger: settings.Logger, + config: config, + mb: metadata.NewMetricsBuilder(config.MetricsBuilderConfig, settings), + mongoVersion: unknownVersion(), + removeDatabaseResourceAttr: removeDatabaseResourceAttrFeatureGate.IsEnabled(), + } + + if !ms.removeDatabaseResourceAttr { + settings.Logger.Warn( + fmt.Sprintf("Feature gate %s is not enabled. Please see the README for more information: %s", removeDatabaseResourceAttrID, readmeURL), + ) } + + return ms } func (s *mongodbScraper) start(ctx context.Context, _ component.Host) error { @@ -116,7 +143,15 @@ func (s *mongodbScraper) collectDatabase(ctx context.Context, now pcommon.Timest return } s.recordNormalServerStats(now, serverStatus, databaseName, errs) - s.mb.EmitForResource() + + emitWith := []metadata.ResourceMetricsOption{} + if !s.removeDatabaseResourceAttr { + rb := s.mb.NewResourceBuilder() + rb.SetDatabase(databaseName) + emitWith = append(emitWith, metadata.WithResource(rb.Emit())) + } + + s.mb.EmitForResource(emitWith...) } func (s *mongodbScraper) collectAdminDatabase(ctx context.Context, now pcommon.Timestamp, errs *scrapererror.ScrapeErrors) {