Skip to content

Commit 471eefb

Browse files
fix(otel): fix beatprocessor to honor when conditions (#50555) (#50561)
* otel(beatprocessor): honor conditions on inner Beat processors The beat processor instantiated each processor by calling its New constructor directly, bypassing the conditional wrapping that processors.Namespace.Register applies on the standard libbeat path. The `when` field was unpacked into the config but silently dropped, so processors ran unconditionally. Wrap each constructor with `processors.NewConditional` before invoking it so `when` configuration is honored, matching the behavior of standalone beats. * add changelog * better wording (cherry picked from commit 7bbe8ee) Co-authored-by: Mauri de Souza Meneguzzo <mauri870@gmail.com>
1 parent 3af4072 commit 471eefb

4 files changed

Lines changed: 185 additions & 7 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Kind can be one of:
2+
# - breaking-change: a change to previously-documented behavior
3+
# - deprecation: functionality that is being removed in a later release
4+
# - bug-fix: fixes a problem in a previous version
5+
# - enhancement: extends functionality but does not break or fix existing behavior
6+
# - feature: new functionality
7+
# - known-issue: problems that we are aware of in a given version
8+
# - security: impacts on the security of a product or a user’s deployment.
9+
# - upgrade: important information for someone upgrading from a prior version
10+
# - other: does not fit into any of the other categories
11+
kind: bug-fix
12+
13+
# Change summary; a 80ish characters long description of the change.
14+
summary: Fix OTel Beat processor to honor `when` conditions
15+
16+
# Long description; in case the summary is not enough to describe the change
17+
# this field accommodate a description without length limits.
18+
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
19+
#description:
20+
21+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
22+
component: all
23+
24+
# PR URL; optional; the PR number that added the changeset.
25+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
26+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
27+
# Please provide it if you are adding a fragment for a different PR.
28+
#pr: https://github.com/owner/repo/1234
29+
30+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
31+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
32+
#issue: https://github.com/owner/repo/issues/1234

x-pack/filebeat/tests/integration/otel_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2174,3 +2174,65 @@ exporters:
21742174
processorInstanceCount := col.ObservedLogs().FilterMessageSnippet("Configured Beat processor").Len()
21752175
assert.Equal(t, 1, processorInstanceCount, "expected beat processor to be configured once (shared instance), but got %d", processorInstanceCount)
21762176
}
2177+
2178+
// TestBeatProcessorWhenCondition verifies that `when` conditions are
2179+
// honored for beat processors.
2180+
func TestBeatProcessorWhenCondition(t *testing.T) {
2181+
cfg := `service:
2182+
pipelines:
2183+
logs:
2184+
receivers:
2185+
- filebeatreceiver
2186+
processors:
2187+
- beat
2188+
exporters:
2189+
- debug
2190+
telemetry:
2191+
logs:
2192+
level: debug
2193+
metrics:
2194+
level: none
2195+
receivers:
2196+
filebeatreceiver:
2197+
filebeat:
2198+
inputs:
2199+
- type: benchmark
2200+
enabled: true
2201+
message: "marker test message"
2202+
count: 1
2203+
queue.mem.flush.timeout: 0s
2204+
processors:
2205+
beat:
2206+
processors:
2207+
- add_fields:
2208+
target: ""
2209+
fields:
2210+
should_be_added: "yes"
2211+
when.contains.message: "marker"
2212+
- add_fields:
2213+
target: ""
2214+
fields:
2215+
should_not_be_added: "yes"
2216+
when.not.contains.message: "marker"
2217+
exporters:
2218+
debug:
2219+
verbosity: detailed
2220+
`
2221+
col := oteltestcol.New(t, cfg)
2222+
require.NotNil(t, col)
2223+
2224+
require.Eventually(t, func() bool {
2225+
return col.ObservedLogs().
2226+
FilterMessageSnippet("Body: Map({").
2227+
FilterMessageSnippet(`"message":"marker test message"`).
2228+
FilterMessageSnippet(`"should_be_added":"yes"`).
2229+
Len() == 1
2230+
}, 30*time.Second, 100*time.Millisecond, "expected event to be enriched")
2231+
2232+
// The processor whose condition does not match must not enrich the event.
2233+
matchingNotAdded := col.ObservedLogs().
2234+
FilterMessageSnippet("Body: Map({").
2235+
FilterMessageSnippet(`"should_not_be_added"`).
2236+
Len()
2237+
assert.Equal(t, 0, matchingNotAdded, "expected `should_not_be_added` field to be absent")
2238+
}

x-pack/otel/processor/beatprocessor/processor.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/elastic/beats/v7/libbeat/beat"
1515
"github.com/elastic/beats/v7/libbeat/otel/otelmap"
16+
"github.com/elastic/beats/v7/libbeat/processors"
1617
"github.com/elastic/beats/v7/libbeat/processors/actions/addfields"
1718
"github.com/elastic/beats/v7/libbeat/processors/add_cloud_metadata"
1819
"github.com/elastic/beats/v7/libbeat/processors/add_docker_metadata"
@@ -82,24 +83,26 @@ func createProcessor(processorNameAndConfig map[string]any, logpLogger *logp.Log
8283
return nil, fmt.Errorf("failed to create config for processor '%s': %w", processorName, configError)
8384
}
8485

85-
var processorInstance beat.Processor
86-
var createProcessorError error
86+
var constructor processors.Constructor
8787

8888
switch processorName {
8989
case "add_cloud_metadata":
90-
processorInstance, createProcessorError = add_cloud_metadata.New(processorConfig, logpLogger)
90+
constructor = add_cloud_metadata.New
9191
case "add_docker_metadata":
92-
processorInstance, createProcessorError = add_docker_metadata.New(processorConfig, logpLogger)
92+
constructor = add_docker_metadata.New
9393
case "add_fields":
94-
processorInstance, createProcessorError = addfields.CreateAddFields(processorConfig, logpLogger)
94+
constructor = addfields.CreateAddFields
9595
case "add_host_metadata":
96-
processorInstance, createProcessorError = add_host_metadata.New(processorConfig, logpLogger)
96+
constructor = add_host_metadata.New
9797
case "add_kubernetes_metadata":
98-
processorInstance, createProcessorError = add_kubernetes_metadata.New(processorConfig, logpLogger)
98+
constructor = add_kubernetes_metadata.New
9999
default:
100100
return nil, fmt.Errorf("invalid processor name '%s'", processorName)
101101
}
102102

103+
// Wrap the constructor with NewConditional so that `when` conditions
104+
// configured on the processor are honored.
105+
processorInstance, createProcessorError := processors.NewConditional(constructor)(processorConfig, logpLogger)
103106
if createProcessorError != nil {
104107
return nil, fmt.Errorf("failed to create processor '%s': %w", processorName, createProcessorError)
105108
}

x-pack/otel/processor/beatprocessor/processor_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,87 @@ func TestCreateProcessor(t *testing.T) {
144144
require.NotNil(t, processor)
145145
assert.Equal(t, "add_kubernetes_metadata", processor.String()[:len("add_kubernetes_metadata")])
146146
})
147+
148+
t.Run("when condition is honored and processor is skipped when condition is false", func(t *testing.T) {
149+
processor, err := createProcessor(map[string]any{
150+
"add_fields": map[string]any{
151+
"target": "",
152+
"fields": map[string]any{
153+
"enriched": "yes",
154+
},
155+
"when": map[string]any{
156+
"contains": map[string]any{
157+
"tags": "forwarded",
158+
},
159+
},
160+
},
161+
}, testLogger())
162+
require.NoError(t, err)
163+
require.NotNil(t, processor)
164+
assert.Contains(t, processor.String(), "condition=", "expected processor to be wrapped with a condition")
165+
166+
event := &beat.Event{Fields: mapstr.M{"message": "hello"}}
167+
out, err := processor.Run(event)
168+
require.NoError(t, err)
169+
_, lookupErr := out.Fields.GetValue("enriched")
170+
assert.Error(t, lookupErr, "expected 'enriched' field to be absent when condition is not met")
171+
})
172+
173+
t.Run("when condition is honored and processor runs when condition is true", func(t *testing.T) {
174+
processor, err := createProcessor(map[string]any{
175+
"add_fields": map[string]any{
176+
"target": "",
177+
"fields": map[string]any{
178+
"enriched": "yes",
179+
},
180+
"when": map[string]any{
181+
"contains": map[string]any{
182+
"tags": "forwarded",
183+
},
184+
},
185+
},
186+
}, testLogger())
187+
require.NoError(t, err)
188+
require.NotNil(t, processor)
189+
190+
event := &beat.Event{Fields: mapstr.M{"message": "hello", "tags": []string{"forwarded"}}}
191+
out, err := processor.Run(event)
192+
require.NoError(t, err)
193+
val, err := out.Fields.GetValue("enriched")
194+
require.NoError(t, err, "expected 'enriched' field to be added when condition is met")
195+
assert.Equal(t, "yes", val)
196+
})
197+
198+
t.Run("when.not.contains skips processor when matching tag is present", func(t *testing.T) {
199+
processor, err := createProcessor(map[string]any{
200+
"add_fields": map[string]any{
201+
"target": "",
202+
"fields": map[string]any{
203+
"enriched": "yes",
204+
},
205+
"when.not.contains.tags": "forwarded",
206+
},
207+
}, testLogger())
208+
require.NoError(t, err)
209+
require.NotNil(t, processor)
210+
211+
event := &beat.Event{Fields: mapstr.M{"message": "hello", "tags": []string{"forwarded"}}}
212+
out, err := processor.Run(event)
213+
require.NoError(t, err)
214+
_, lookupErr := out.Fields.GetValue("enriched")
215+
assert.Error(t, lookupErr, "expected 'enriched' field to be absent when 'forwarded' tag is present")
216+
})
217+
218+
t.Run("invalid when condition returns error", func(t *testing.T) {
219+
_, err := createProcessor(map[string]any{
220+
"add_host_metadata": map[string]any{
221+
"when": map[string]any{
222+
"not_a_real_condition": map[string]any{},
223+
},
224+
},
225+
}, testLogger())
226+
require.Error(t, err)
227+
})
147228
}
148229

149230
func testLogger() *logp.Logger {

0 commit comments

Comments
 (0)