16
16
17
17
package org .springframework .kafka .support .micrometer ;
18
18
19
+ import java .nio .charset .StandardCharsets ;
19
20
import java .util .Arrays ;
20
21
import java .util .Deque ;
21
22
import java .util .List ;
26
27
import java .util .concurrent .TimeUnit ;
27
28
import java .util .concurrent .TimeoutException ;
28
29
import java .util .concurrent .atomic .AtomicReference ;
30
+ import java .util .stream .StreamSupport ;
29
31
30
32
import io .micrometer .common .KeyValues ;
31
33
import io .micrometer .core .instrument .MeterRegistry ;
56
58
import org .apache .kafka .common .errors .InvalidTopicException ;
57
59
import org .apache .kafka .common .header .Header ;
58
60
import org .apache .kafka .common .header .Headers ;
61
+ import org .apache .kafka .common .header .internals .RecordHeader ;
59
62
import org .jspecify .annotations .Nullable ;
60
63
import org .junit .jupiter .api .Test ;
61
64
import reactor .core .publisher .Mono ;
78
81
import org .springframework .kafka .core .ProducerFactory ;
79
82
import org .springframework .kafka .listener .MessageListenerContainer ;
80
83
import org .springframework .kafka .listener .RecordInterceptor ;
84
+ import org .springframework .kafka .support .ProducerListener ;
81
85
import org .springframework .kafka .support .micrometer .KafkaListenerObservation .DefaultKafkaListenerObservationConvention ;
82
86
import org .springframework .kafka .support .micrometer .KafkaTemplateObservation .DefaultKafkaTemplateObservationConvention ;
83
87
import org .springframework .kafka .test .EmbeddedKafkaBroker ;
104
108
@ SpringJUnitConfig
105
109
@ EmbeddedKafka (topics = { ObservationTests .OBSERVATION_TEST_1 , ObservationTests .OBSERVATION_TEST_2 ,
106
110
ObservationTests .OBSERVATION_TEST_3 , ObservationTests .OBSERVATION_RUNTIME_EXCEPTION ,
107
- ObservationTests .OBSERVATION_ERROR }, partitions = 1 )
111
+ ObservationTests .OBSERVATION_ERROR , ObservationTests . OBSERVATION_TRACEPARENT_DUPLICATE }, partitions = 1 )
108
112
@ DirtiesContext
109
113
public class ObservationTests {
110
114
@@ -122,6 +126,8 @@ public class ObservationTests {
122
126
123
127
public final static String OBSERVATION_ERROR_MONO = "observation.error.mono" ;
124
128
129
+ public final static String OBSERVATION_TRACEPARENT_DUPLICATE = "observation.traceparent.duplicate" ;
130
+
125
131
@ Test
126
132
void endToEnd (@ Autowired Listener listener , @ Autowired KafkaTemplate <Integer , String > template ,
127
133
@ Autowired SimpleTracer tracer , @ Autowired KafkaListenerEndpointRegistry rler ,
@@ -449,6 +455,62 @@ void kafkaAdminNotRecreatedIfBootstrapServersSameInProducerAndAdminConfig(
449
455
assertThat (template .getKafkaAdmin ()).isSameAs (kafkaAdmin );
450
456
}
451
457
458
+ @ Test
459
+ void verifyKafkaRecordSenderContextTraceParentHandling () {
460
+ String initialTraceParent = "traceparent-from-previous" ;
461
+ String updatedTraceParent = "traceparent-current" ;
462
+ ProducerRecord <Integer , String > record = new ProducerRecord <>("test-topic" , "test-value" );
463
+ record .headers ().add ("traceparent" , initialTraceParent .getBytes (StandardCharsets .UTF_8 ));
464
+
465
+ // Create the context and update the traceparent
466
+ KafkaRecordSenderContext context = new KafkaRecordSenderContext (
467
+ record ,
468
+ "test-bean" ,
469
+ () -> "test-cluster"
470
+ );
471
+ context .getSetter ().set (record , "traceparent" , updatedTraceParent );
472
+
473
+ Iterable <Header > traceparentHeaders = record .headers ().headers ("traceparent" );
474
+
475
+ List <String > headerValues = StreamSupport .stream (traceparentHeaders .spliterator (), false )
476
+ .map (header -> new String (header .value (), StandardCharsets .UTF_8 ))
477
+ .toList ();
478
+
479
+ // Verify there's only one traceparent header and it contains the updated value
480
+ assertThat (headerValues ).containsExactly (updatedTraceParent );
481
+ }
482
+
483
+ @ Test
484
+ void verifyTraceParentHeader (@ Autowired KafkaTemplate <Integer , String > template ,
485
+ @ Autowired SimpleTracer tracer ) throws Exception {
486
+ CompletableFuture <ProducerRecord <Integer , String >> producerRecordFuture = new CompletableFuture <>();
487
+ template .setProducerListener (new ProducerListener <>() {
488
+ @ Override
489
+ public void onSuccess (ProducerRecord <Integer , String > producerRecord , RecordMetadata recordMetadata ) {
490
+ producerRecordFuture .complete (producerRecord );
491
+ }
492
+ });
493
+ String initialTraceParent = "traceparent-from-previous" ;
494
+ Header header = new RecordHeader ("traceparent" , initialTraceParent .getBytes (StandardCharsets .UTF_8 ));
495
+ ProducerRecord <Integer , String > producerRecord = new ProducerRecord <>(
496
+ OBSERVATION_TRACEPARENT_DUPLICATE ,
497
+ null , null , null ,
498
+ "test-value" ,
499
+ List .of (header )
500
+ );
501
+
502
+ template .send (producerRecord ).get (10 , TimeUnit .SECONDS );
503
+ ProducerRecord <Integer , String > recordResult = producerRecordFuture .get (10 , TimeUnit .SECONDS );
504
+
505
+ Iterable <Header > traceparentHeaders = recordResult .headers ().headers ("traceparent" );
506
+ assertThat (traceparentHeaders ).hasSize (1 );
507
+
508
+ String traceparentValue = new String (traceparentHeaders .iterator ().next ().value (), StandardCharsets .UTF_8 );
509
+ assertThat (traceparentValue ).isEqualTo ("traceparent-from-propagator" );
510
+
511
+ tracer .getSpans ().clear ();
512
+ }
513
+
452
514
@ Configuration
453
515
@ EnableKafka
454
516
public static class Config {
@@ -598,6 +660,9 @@ public List<String> fields() {
598
660
public <C > void inject (TraceContext context , @ Nullable C carrier , Setter <C > setter ) {
599
661
setter .set (carrier , "foo" , "some foo value" );
600
662
setter .set (carrier , "bar" , "some bar value" );
663
+
664
+ // Add a traceparent header to simulate W3C trace context
665
+ setter .set (carrier , "traceparent" , "traceparent-from-propagator" );
601
666
}
602
667
603
668
// This is called on the consumer side when the message is consumed
@@ -606,7 +671,9 @@ public <C> void inject(TraceContext context, @Nullable C carrier, Setter<C> sett
606
671
public <C > Span .Builder extract (C carrier , Getter <C > getter ) {
607
672
String foo = getter .get (carrier , "foo" );
608
673
String bar = getter .get (carrier , "bar" );
609
- return tracer .spanBuilder ().tag ("foo" , foo ).tag ("bar" , bar );
674
+ return tracer .spanBuilder ()
675
+ .tag ("foo" , foo )
676
+ .tag ("bar" , bar );
610
677
}
611
678
};
612
679
}
0 commit comments