77package integration
88
99import (
10+ "encoding/json"
1011 "fmt"
1112 "path/filepath"
1213 "testing"
1314 "time"
1415
1516 "github.com/gofrs/uuid/v5"
16- "github.com/google/go-cmp/cmp "
17+ "github.com/stretchr/testify/assert "
1718 "github.com/stretchr/testify/require"
18- "go.opentelemetry.io/collector/pdata/pcommon"
19- "go.opentelemetry.io/collector/pdata/plog"
2019
21- "github.com/elastic/beats/v7/libbeat/otel/otelmap"
20+ "github.com/elastic/beats/v7/libbeat/tests/integration"
21+ "github.com/elastic/beats/v7/x-pack/otel/oteltest"
2222 "github.com/elastic/beats/v7/x-pack/otel/oteltestcol"
2323 "github.com/elastic/elastic-agent-libs/mapstr"
2424 "github.com/elastic/sarama"
@@ -27,15 +27,19 @@ import (
2727// kafkaBroker is the Kafka OUTSIDE listener, which advertises localhost:9094
2828const kafkaBroker = "localhost:9094"
2929
30- func TestFilebeatOTelKafkaExporter (t * testing.T ) {
31- topic := fmt . Sprintf ( "test-otel-kafka-%s" , uuid . Must ( uuid . NewV4 ()). String ())
30+ func TestFilebeatOTelKafkaE2E (t * testing.T ) {
31+ numEvents := 1
3232
3333 tmpdir := t .TempDir ()
34- logFilePath := filepath .Join (tmpdir , "kafka_test.log" )
35- writeEventsToLogFile (t , logFilePath , 1 )
34+ fbTopic := fmt .Sprintf ("test-fb-kafka-%s" , uuid .Must (uuid .NewV4 ()).String ())
35+ otelTopic := fmt .Sprintf ("test-otel-kafka-%s" , uuid .Must (uuid .NewV4 ()).String ())
36+
37+ logFilePath := filepath .Join (tmpdir , "kafka_e2e.log" )
38+ writeEventsToLogFile (t , logFilePath , numEvents )
3639
3740 otelCfg := fmt .Sprintf (`receivers:
3841 filebeatreceiver:
42+ include_metadata: true
3943 filebeat:
4044 inputs:
4145 - type: filestream
@@ -45,6 +49,11 @@ func TestFilebeatOTelKafkaExporter(t *testing.T) {
4549 - %s
4650 prospector.scanner.fingerprint.enabled: false
4751 file_identity.native: ~
52+ processors:
53+ - add_host_metadata: ~
54+ - add_cloud_metadata: ~
55+ - add_docker_metadata: ~
56+ - add_kubernetes_metadata: ~
4857 queue.mem.flush.timeout: 0s
4958 setup.template.enabled: false
5059 path.home: %s
@@ -54,7 +63,7 @@ exporters:
5463 - %s
5564 logs:
5665 topic: %s
57- encoding: otlp_json
66+ encoding: raw
5867service:
5968 pipelines:
6069 logs:
@@ -65,10 +74,68 @@ service:
6574 telemetry:
6675 metrics:
6776 level: none
68- ` , logFilePath , tmpdir , kafkaBroker , topic )
77+ ` , logFilePath , tmpdir , kafkaBroker , otelTopic )
6978
7079 oteltestcol .New (t , otelCfg )
7180
81+ fbCfg := fmt .Sprintf (`
82+ filebeat.inputs:
83+ - type: filestream
84+ id: filestream-input-id
85+ enabled: true
86+ file_identity.native: ~
87+ prospector.scanner.fingerprint.enabled: false
88+ paths:
89+ - %s
90+ output:
91+ kafka:
92+ hosts:
93+ - %s
94+ topic: %s
95+ queue.mem.flush.timeout: 0s
96+ setup.template.enabled: false
97+ processors:
98+ - add_host_metadata: ~
99+ - add_cloud_metadata: ~
100+ - add_docker_metadata: ~
101+ - add_kubernetes_metadata: ~
102+ ` , logFilePath , kafkaBroker , fbTopic )
103+
104+ filebeat := integration .NewBeat (t , "filebeat" , "../../filebeat.test" )
105+ filebeat .WriteConfigFile (fbCfg )
106+ filebeat .Start ()
107+ defer filebeat .Stop ()
108+
109+ otelMsg := consumeKafkaTopic (t , otelTopic )
110+ fbMsg := consumeKafkaTopic (t , fbTopic )
111+
112+ t .Logf ("otel kafka message: %s" , string (otelMsg ))
113+ t .Logf ("filebeat kafka message: %s" , string (fbMsg ))
114+
115+ var otelBody , fbBody mapstr.M
116+ require .NoError (t , json .Unmarshal (otelMsg , & otelBody ), "OTel kafka message is not valid JSON" )
117+ require .NoError (t , json .Unmarshal (fbMsg , & fbBody ), "filebeat kafka message is not valid JSON" )
118+
119+ assert .NotEmpty (t , otelBody ["@metadata" ], "expected @metadata to be present in OTel kafka message" )
120+
121+ ignoredFields := []string {
122+ "@timestamp" ,
123+ "agent.ephemeral_id" ,
124+ "agent.id" ,
125+ "log.file.inode" ,
126+ "log.file.device_id" ,
127+ }
128+
129+ oteltest .AssertMapsEqual (t , fbBody , otelBody , ignoredFields , "expected documents to be equal" )
130+
131+ assert .Equal (t , "filebeat" , otelBody .Flatten ()["agent.type" ], "expected agent.type to be 'filebeat' in otel doc" )
132+ assert .Equal (t , "filebeat" , fbBody .Flatten ()["agent.type" ], "expected agent.type to be 'filebeat' in filebeat doc" )
133+ }
134+
135+ // consumeKafkaTopic waits for a topic to appear and returns the first message received.
136+ func consumeKafkaTopic (t * testing.T , topic string ) []byte {
137+ t .Helper ()
138+
72139 cfg := sarama .NewConfig ()
73140 cfg .Consumer .Return .Errors = true
74141
@@ -88,14 +155,12 @@ service:
88155 require .NoError (t , err , "failed to create Kafka consumer" )
89156 t .Cleanup (func () { _ = consumer .Close () })
90157
91- // Wait for the topic
92158 msgs := make (chan * sarama.ConsumerMessage , 10 )
93159 require .Eventually (t , func () bool {
94160 partitions , err := consumer .Partitions (topic )
95161 if err != nil || len (partitions ) == 0 {
96162 return false
97163 }
98- // Spin up one goroutine per partition forwarding into msgs.
99164 for _ , partition := range partitions {
100165 pc , err := consumer .ConsumePartition (topic , partition , sarama .OffsetOldest )
101166 if err != nil {
@@ -111,7 +176,6 @@ service:
111176 return true
112177 }, 30 * time .Second , 500 * time .Millisecond , "topic %q did not appear within 30s" , topic )
113178
114- // Poll until at least one message arrives.
115179 var received []byte
116180 require .Eventually (t , func () bool {
117181 select {
@@ -123,57 +187,5 @@ service:
123187 }
124188 }, 30 * time .Second , 500 * time .Millisecond , "no message received from Kafka topic %q" , topic )
125189
126- t .Logf ("received Kafka message: %s" , string (received ))
127-
128- // Decode the OTLP JSON payload.
129- unmarshaler := & plog.JSONUnmarshaler {}
130- logs , err := unmarshaler .UnmarshalLogs (received )
131- require .NoError (t , err , "Kafka message is not valid OTLP JSON" )
132- require .Equal (t , 1 , logs .ResourceLogs ().Len (), "expected 1 resource log" )
133-
134- scopeLogs := logs .ResourceLogs ().At (0 ).ScopeLogs ()
135- require .Equal (t , 1 , scopeLogs .Len (), "expected 1 scope log" )
136-
137- logRecords := scopeLogs .At (0 ).LogRecords ()
138- require .Equal (t , 1 , logRecords .Len (), "expected 1 log record" )
139-
140- body := logRecords .At (0 ).Body ()
141- require .Equal (t , pcommon .ValueTypeMap , body .Type (), "expected log record body to be a map (bodymap encoding)" )
142-
143- got := otelmap .ToMapstr (body .Map ()).Flatten ()
144-
145- // Check non-deterministic fields are present
146- agentVersion , _ := got .GetValue ("agent.version" )
147- require .NotEmpty (t , agentVersion , "expected agent.version to be set" )
148- agentID , _ := got .GetValue ("agent.id" )
149- require .NotEmpty (t , agentID , "expected agent.id to be set" )
150- agentEphemeralID , _ := got .GetValue ("agent.ephemeral_id" )
151- require .NotEmpty (t , agentEphemeralID , "expected agent.ephemeral_id to be set" )
152- hostName , _ := got .GetValue ("host.name" )
153- require .NotEmpty (t , hostName , "expected host.name to be set" )
154- timestamp , _ := got .GetValue ("@timestamp" )
155- require .NotEmpty (t , timestamp , "expected @timestamp to be set" )
156-
157- // Remove non-deterministic fields before comparison.
158- _ = got .Delete ("@timestamp" )
159- _ = got .Delete ("agent.id" )
160- _ = got .Delete ("agent.ephemeral_id" )
161- _ = got .Delete ("agent.name" )
162- _ = got .Delete ("agent.version" )
163- _ = got .Delete ("host.name" )
164- _ = got .Delete ("log.file.device_id" )
165- _ = got .Delete ("log.file.inode" )
166-
167- want := mapstr.M {
168- "message" : "Line 0" ,
169- "agent.type" : "filebeat" ,
170- "input.type" : "filestream" ,
171- "ecs.version" : "8.0.0" ,
172- "log.offset" : int64 (0 ),
173- "log.file.path" : logFilePath ,
174- }
175-
176- if diff := cmp .Diff (want , got ); diff != "" {
177- t .Fatalf ("log event fields mismatch (-want +got):\n %s" , diff )
178- }
190+ return received
179191}
0 commit comments