diff --git a/.chloggen/filterprocessor-beta-default-error-mode-ignore.yaml b/.chloggen/filterprocessor-beta-default-error-mode-ignore.yaml new file mode 100644 index 0000000000000..f084de8dc20c4 --- /dev/null +++ b/.chloggen/filterprocessor-beta-default-error-mode-ignore.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: breaking + +# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog) +component: processor/filter + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Promote `processor.filter.defaultErrorModeIgnore` feature gate to beta. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [47232] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: The default `error_mode` of the filter processor is now `ignore` instead of `propagate`. To restore the previous behavior, disable the feature gate with `--feature-gates=-processor.filter.defaultErrorModeIgnore`. + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user] diff --git a/processor/filterprocessor/README.md b/processor/filterprocessor/README.md index 6d388be9ab2ef..d76fa01808784 100644 --- a/processor/filterprocessor/README.md +++ b/processor/filterprocessor/README.md @@ -95,7 +95,7 @@ This condition translates to: For each span event, check whether its parent span ### Error Modes -The filter processor also allows configuring an optional field, `error_mode`, which will determine how the processor reacts to errors that occur while processing an OTTL condition. `propagate` is the default mode. +The filter processor also allows configuring an optional field, `error_mode`, which will determine how the processor reacts to errors that occur while processing an OTTL condition. `ignore` is the default mode. | error_mode | description | |------------|----------------------------------------------------------------------------------------------------------------------------------------| @@ -107,13 +107,13 @@ The filter processor also allows configuring an optional field, `error_mode`, wh ##### `processor.filter.defaultErrorModeIgnore` -The `processor.filter.defaultErrorModeIgnore` feature gate changes the default `error_mode` of the filter processor from `propagate` to `ignore`. +The `processor.filter.defaultErrorModeIgnore` feature gate changes the default `error_mode` of the filter processor from `propagate` to `ignore`. `ignore` is the recommended mode to improve resiliency, as errors are logged for visibility but valid data is preserved, and processing continues with the next condition. -This feature gate is currently in Alpha (disabled by default) and must be explicitly enabled. +This feature gate is currently in Beta (enabled by default). **Example Usage** -Run the collector with the feature gate enabled: `./otelcol --config config.yaml --feature-gates=processor.filter.defaultErrorModeIgnore`. +To restore the previous default of `propagate`, run the collector with the feature gate disabled: `./otelcol --config config.yaml --feature-gates=-processor.filter.defaultErrorModeIgnore`. ### Basic Config diff --git a/processor/filterprocessor/config.go b/processor/filterprocessor/config.go index 8da415a80fecd..537e669ce77ca 100644 --- a/processor/filterprocessor/config.go +++ b/processor/filterprocessor/config.go @@ -36,7 +36,7 @@ type Config struct { // Valid values are `ignore` and `propagate`. // `ignore` means the processor ignores errors returned by conditions and continues on to the next condition. This is the recommended mode. // `propagate` means the processor returns the error up the pipeline. This will result in the payload being dropped from the collector. - // The default value is `propagate`. It will change to `ignore` when the `processor.filter.defaultErrorModeIgnore` feature gate is stable. + // The default value is `ignore` when the `processor.filter.defaultErrorModeIgnore` feature gate is enabled (default), and `propagate` when disabled. ErrorMode ottl.ErrorMode `mapstructure:"error_mode"` // Deprecated: use TraceConditions instead. diff --git a/processor/filterprocessor/config.schema.yaml b/processor/filterprocessor/config.schema.yaml index 3f2c5b5a092a4..5784b8cb6c1be 100644 --- a/processor/filterprocessor/config.schema.yaml +++ b/processor/filterprocessor/config.schema.yaml @@ -132,7 +132,7 @@ description: Config defines configuration for Resource processor. type: object properties: error_mode: - description: ErrorMode determines how the processor reacts to errors that occur while processing an OTTL condition. Valid values are `ignore` and `propagate`. `ignore` means the processor ignores errors returned by conditions and continues on to the next condition. This is the recommended mode. `propagate` means the processor returns the error up the pipeline. This will result in the payload being dropped from the collector. The default value is `propagate`. It will change to `ignore` when the `processor.filter.defaultErrorModeIgnore` feature gate is stable. + description: ErrorMode determines how the processor reacts to errors that occur while processing an OTTL condition. Valid values are `ignore` and `propagate`. `ignore` means the processor ignores errors returned by conditions and continues on to the next condition. This is the recommended mode. `propagate` means the processor returns the error up the pipeline. This will result in the payload being dropped from the collector. The default value is `ignore` when the `processor.filter.defaultErrorModeIgnore` feature gate is enabled (default), and `propagate` when disabled. $ref: /pkg/ottl.error_mode log_conditions: type: array diff --git a/processor/filterprocessor/config_test.go b/processor/filterprocessor/config_test.go index 8f0eb2a6e15da..a30bb09b62eec 100644 --- a/processor/filterprocessor/config_test.go +++ b/processor/filterprocessor/config_test.go @@ -68,7 +68,7 @@ func TestLoadingConfigStrict(t *testing.T) { { id: component.MustNewIDWithName("filter", "empty"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Include: &filterconfig.MetricMatchProperties{ MatchType: filterconfig.MetricStrict, @@ -78,7 +78,7 @@ func TestLoadingConfigStrict(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "include"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Include: testDataMetricProperties, }, @@ -86,7 +86,7 @@ func TestLoadingConfigStrict(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "exclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Exclude: testDataMetricProperties, }, @@ -94,7 +94,7 @@ func TestLoadingConfigStrict(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "includeexclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Include: testDataMetricProperties, Exclude: &filterconfig.MetricMatchProperties{ @@ -154,7 +154,7 @@ func TestLoadingConfigStrictLogs(t *testing.T) { { id: component.MustNewIDWithName("filter", "empty"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: &LogMatchProperties{ LogMatchType: strictType, @@ -164,7 +164,7 @@ func TestLoadingConfigStrictLogs(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "include"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, }, @@ -172,7 +172,7 @@ func TestLoadingConfigStrictLogs(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "exclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Exclude: testDataLogPropertiesExclude, }, @@ -180,7 +180,7 @@ func TestLoadingConfigStrictLogs(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "includeexclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, Exclude: testDataLogPropertiesExclude, @@ -227,7 +227,7 @@ func TestLoadingConfigSeverityLogsStrict(t *testing.T) { { id: component.MustNewIDWithName("filter", "include"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, }, @@ -235,7 +235,7 @@ func TestLoadingConfigSeverityLogsStrict(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "exclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Exclude: testDataLogPropertiesExclude, }, @@ -243,7 +243,7 @@ func TestLoadingConfigSeverityLogsStrict(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "includeexclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, Exclude: testDataLogPropertiesExclude, @@ -290,7 +290,7 @@ func TestLoadingConfigSeverityLogsRegexp(t *testing.T) { { id: component.MustNewIDWithName("filter", "include"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, }, @@ -298,7 +298,7 @@ func TestLoadingConfigSeverityLogsRegexp(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "exclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Exclude: testDataLogPropertiesExclude, }, @@ -306,7 +306,7 @@ func TestLoadingConfigSeverityLogsRegexp(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "includeexclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, Exclude: testDataLogPropertiesExclude, @@ -353,7 +353,7 @@ func TestLoadingConfigBodyLogsStrict(t *testing.T) { { id: component.MustNewIDWithName("filter", "include"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, }, @@ -361,7 +361,7 @@ func TestLoadingConfigBodyLogsStrict(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "exclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Exclude: testDataLogPropertiesExclude, }, @@ -369,7 +369,7 @@ func TestLoadingConfigBodyLogsStrict(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "includeexclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, Exclude: testDataLogPropertiesExclude, @@ -416,7 +416,7 @@ func TestLoadingConfigBodyLogsRegexp(t *testing.T) { { id: component.MustNewIDWithName("filter", "include"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, }, @@ -424,7 +424,7 @@ func TestLoadingConfigBodyLogsRegexp(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "exclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Exclude: testDataLogPropertiesExclude, }, @@ -432,7 +432,7 @@ func TestLoadingConfigBodyLogsRegexp(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "includeexclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, Exclude: testDataLogPropertiesExclude, @@ -482,7 +482,7 @@ func TestLoadingConfigMinSeverityNumberLogs(t *testing.T) { { id: component.MustNewIDWithName("filter", "include"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, }, @@ -490,7 +490,7 @@ func TestLoadingConfigMinSeverityNumberLogs(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "exclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Exclude: testDataLogPropertiesExclude, }, @@ -498,7 +498,7 @@ func TestLoadingConfigMinSeverityNumberLogs(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "includeexclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Logs: LogFilters{ Include: testDataLogPropertiesInclude, Exclude: testDataLogPropertiesExclude, @@ -552,7 +552,7 @@ func TestLoadingConfigRegexp(t *testing.T) { { id: component.MustNewIDWithName("filter", "include"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Include: testDataMetricProperties, }, @@ -560,7 +560,7 @@ func TestLoadingConfigRegexp(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "exclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Exclude: testDataMetricProperties, }, @@ -568,7 +568,7 @@ func TestLoadingConfigRegexp(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "unlimitedcache"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Include: &filterconfig.MetricMatchProperties{ MatchType: filterconfig.MetricRegexp, @@ -582,7 +582,7 @@ func TestLoadingConfigRegexp(t *testing.T) { }, { id: component.MustNewIDWithName("filter", "limitedcache"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Exclude: &filterconfig.MetricMatchProperties{ MatchType: filterconfig.MetricRegexp, @@ -624,7 +624,7 @@ func TestLoadingSpans(t *testing.T) { { id: component.MustNewIDWithName("filter", "spans"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Spans: filterconfig.MatchConfig{ Include: &filterconfig.MatchProperties{ Config: filterset.Config{ @@ -675,7 +675,7 @@ func TestLoadingConfigExpr(t *testing.T) { { id: component.MustNewIDWithName("filter", "empty"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Include: &filterconfig.MetricMatchProperties{ MatchType: filterconfig.MetricExpr, @@ -686,7 +686,7 @@ func TestLoadingConfigExpr(t *testing.T) { { id: component.MustNewIDWithName("filter", "include"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Include: &filterconfig.MetricMatchProperties{ MatchType: filterconfig.MetricExpr, @@ -701,7 +701,7 @@ func TestLoadingConfigExpr(t *testing.T) { { id: component.MustNewIDWithName("filter", "exclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Exclude: &filterconfig.MetricMatchProperties{ MatchType: filterconfig.MetricExpr, @@ -716,7 +716,7 @@ func TestLoadingConfigExpr(t *testing.T) { { id: component.MustNewIDWithName("filter", "includeexclude"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Metrics: MetricFilters{ Include: &filterconfig.MetricMatchProperties{ MatchType: filterconfig.MetricExpr, @@ -935,7 +935,7 @@ func TestLoadingConfigOTTL(t *testing.T) { { id: component.MustNewIDWithName("filter", "multiline"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, Traces: TraceFilters{ SpanConditions: []string{ `attributes["test"] == "pass"`, @@ -974,7 +974,7 @@ func TestLoadingConfigOTTL(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "context_inferred_trace"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, TraceConditions: []condition.ContextConditions{ { Conditions: []string{`span.attributes["test"] == "pass"`}, @@ -986,7 +986,7 @@ func TestLoadingConfigOTTL(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "context_inferred_metric"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, MetricConditions: []condition.ContextConditions{ { Conditions: []string{`metric.name == "pass"`}, @@ -998,7 +998,7 @@ func TestLoadingConfigOTTL(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "context_inferred_log"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, LogConditions: []condition.ContextConditions{ { Conditions: []string{`log.attributes["test"] == "pass"`}, @@ -1010,7 +1010,7 @@ func TestLoadingConfigOTTL(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "context_inferred_profile"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, ProfileConditions: []condition.ContextConditions{ { Conditions: []string{`profile.attributes["test"] == "pass"`}, @@ -1034,7 +1034,7 @@ func TestLoadingConfigOTTL(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "context_inferred_multiple_conditions"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, TraceConditions: []condition.ContextConditions{ { Conditions: []string{ @@ -1083,7 +1083,7 @@ func TestLoadingConfigOTTL(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "flat_style"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, TraceConditions: getInferredContextConditions("span"), MetricConditions: getInferredContextConditions("datapoint"), LogConditions: getInferredContextConditions("log"), @@ -1093,7 +1093,7 @@ func TestLoadingConfigOTTL(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "advance_style"), expected: &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, TraceConditions: []condition.ContextConditions{ getDefinedContextConditions("span"), getDefinedContextConditions("spanevent"), diff --git a/processor/filterprocessor/documentation.md b/processor/filterprocessor/documentation.md index 79c82c567d0db..eff6cf30d3f1d 100644 --- a/processor/filterprocessor/documentation.md +++ b/processor/filterprocessor/documentation.md @@ -44,6 +44,6 @@ This component has the following feature gates: | Feature Gate | Stage | Description | From Version | To Version | Reference | | ------------ | ----- | ----------- | ------------ | ---------- | --------- | -| `processor.filter.defaultErrorModeIgnore` | alpha | Changes the default error_mode of the filter processor from propagate to ignore | v0.150.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/47232) | +| `processor.filter.defaultErrorModeIgnore` | beta | Changes the default error_mode of the filter processor from propagate to ignore | v0.150.0 | N/A | [Link](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/47232) | For more information about feature gates, see the [Feature Gates](https://github.com/open-telemetry/opentelemetry-collector/blob/main/featuregate/README.md) documentation. diff --git a/processor/filterprocessor/factory_test.go b/processor/filterprocessor/factory_test.go index 82e977378bdf3..9107b4c1d8056 100644 --- a/processor/filterprocessor/factory_test.go +++ b/processor/filterprocessor/factory_test.go @@ -41,20 +41,20 @@ func TestCreateDefaultConfig(t *testing.T) { cfg := factory.CreateDefaultConfig() assert.EqualExportedValues(t, &Config{ - ErrorMode: ottl.PropagateError, + ErrorMode: ottl.IgnoreError, }, cfg) assertConfigContainsDefaultFunctions(t, *cfg.(*Config)) assert.NoError(t, componenttest.CheckConfigStruct(cfg)) t.Cleanup(func() { - _ = featuregate.GlobalRegistry().Set(metadata.ProcessorFilterDefaultErrorModeIgnoreFeatureGate.ID(), false) + _ = featuregate.GlobalRegistry().Set(metadata.ProcessorFilterDefaultErrorModeIgnoreFeatureGate.ID(), true) }) - err := featuregate.GlobalRegistry().Set(metadata.ProcessorFilterDefaultErrorModeIgnoreFeatureGate.ID(), true) + err := featuregate.GlobalRegistry().Set(metadata.ProcessorFilterDefaultErrorModeIgnoreFeatureGate.ID(), false) require.NoError(t, err) cfg = factory.CreateDefaultConfig() - assert.Equal(t, ottl.IgnoreError, cfg.(*Config).ErrorMode) + assert.Equal(t, ottl.PropagateError, cfg.(*Config).ErrorMode) } func TestCreateProcessors(t *testing.T) { diff --git a/processor/filterprocessor/internal/metadata/generated_feature_gates.go b/processor/filterprocessor/internal/metadata/generated_feature_gates.go index c4e4e552ef008..484f340a960d9 100644 --- a/processor/filterprocessor/internal/metadata/generated_feature_gates.go +++ b/processor/filterprocessor/internal/metadata/generated_feature_gates.go @@ -8,7 +8,7 @@ import ( var ProcessorFilterDefaultErrorModeIgnoreFeatureGate = featuregate.GlobalRegistry().MustRegister( "processor.filter.defaultErrorModeIgnore", - featuregate.StageAlpha, + featuregate.StageBeta, featuregate.WithRegisterDescription("Changes the default error_mode of the filter processor from propagate to ignore"), featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/47232"), featuregate.WithRegisterFromVersion("v0.150.0"), diff --git a/processor/filterprocessor/metadata.yaml b/processor/filterprocessor/metadata.yaml index 9c5d32969c0da..42d161ac2e119 100644 --- a/processor/filterprocessor/metadata.yaml +++ b/processor/filterprocessor/metadata.yaml @@ -17,7 +17,7 @@ feature_gates: - id: processor.filter.defaultErrorModeIgnore description: >- Changes the default error_mode of the filter processor from propagate to ignore - stage: alpha + stage: beta from_version: "v0.150.0" reference_url: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/47232