17
17
package org .springframework .kafka .support .micrometer ;
18
18
19
19
import java .nio .charset .StandardCharsets ;
20
+ import java .time .Duration ;
20
21
import java .util .Arrays ;
21
22
import java .util .Deque ;
22
23
import java .util .List ;
59
60
import org .apache .kafka .common .header .Header ;
60
61
import org .apache .kafka .common .header .Headers ;
61
62
import org .apache .kafka .common .header .internals .RecordHeader ;
63
+ import org .awaitility .Awaitility ;
62
64
import org .jspecify .annotations .Nullable ;
63
65
import org .junit .jupiter .api .Test ;
64
66
import reactor .core .publisher .Mono ;
81
83
import org .springframework .kafka .core .ProducerFactory ;
82
84
import org .springframework .kafka .listener .MessageListenerContainer ;
83
85
import org .springframework .kafka .listener .RecordInterceptor ;
86
+ import org .springframework .kafka .requestreply .ReplyingKafkaTemplate ;
84
87
import org .springframework .kafka .support .ProducerListener ;
85
88
import org .springframework .kafka .support .micrometer .KafkaListenerObservation .DefaultKafkaListenerObservationConvention ;
86
89
import org .springframework .kafka .support .micrometer .KafkaTemplateObservation .DefaultKafkaTemplateObservationConvention ;
87
90
import org .springframework .kafka .test .EmbeddedKafkaBroker ;
88
91
import org .springframework .kafka .test .context .EmbeddedKafka ;
89
92
import org .springframework .kafka .test .utils .KafkaTestUtils ;
93
+ import org .springframework .messaging .handler .annotation .SendTo ;
90
94
import org .springframework .test .annotation .DirtiesContext ;
91
95
import org .springframework .test .context .junit .jupiter .SpringJUnitConfig ;
92
96
import org .springframework .util .StringUtils ;
107
111
*/
108
112
@ SpringJUnitConfig
109
113
@ EmbeddedKafka (topics = { ObservationTests .OBSERVATION_TEST_1 , ObservationTests .OBSERVATION_TEST_2 ,
110
- ObservationTests .OBSERVATION_TEST_3 , ObservationTests .OBSERVATION_RUNTIME_EXCEPTION ,
111
- ObservationTests .OBSERVATION_ERROR , ObservationTests .OBSERVATION_TRACEPARENT_DUPLICATE }, partitions = 1 )
114
+ ObservationTests .OBSERVATION_TEST_3 , ObservationTests .OBSERVATION_TEST_4 , ObservationTests .OBSERVATION_REPLY ,
115
+ ObservationTests .OBSERVATION_RUNTIME_EXCEPTION , ObservationTests .OBSERVATION_ERROR ,
116
+ ObservationTests .OBSERVATION_TRACEPARENT_DUPLICATE }, partitions = 1 )
112
117
@ DirtiesContext
113
118
public class ObservationTests {
114
119
@@ -118,6 +123,10 @@ public class ObservationTests {
118
123
119
124
public final static String OBSERVATION_TEST_3 = "observation.testT3" ;
120
125
126
+ public final static String OBSERVATION_TEST_4 = "observation.testT4" ;
127
+
128
+ public final static String OBSERVATION_REPLY = "observation.reply" ;
129
+
121
130
public final static String OBSERVATION_RUNTIME_EXCEPTION = "observation.runtime-exception" ;
122
131
123
132
public final static String OBSERVATION_ERROR = "observation.error.sync" ;
@@ -511,6 +520,20 @@ public void onSuccess(ProducerRecord<Integer, String> producerRecord, RecordMeta
511
520
tracer .getSpans ().clear ();
512
521
}
513
522
523
+ @ Test
524
+ void testReplyingKafkaTemplateObservation (
525
+ @ Autowired ReplyingKafkaTemplate <Integer , String , String > template ,
526
+ @ Autowired ObservationRegistry observationRegistry ) {
527
+ AtomicReference <KafkaRecordReceiverContext > replyObservationContext = new AtomicReference <>();
528
+ template .sendAndReceive (new ProducerRecord <>(OBSERVATION_TEST_4 , "test" )).thenAccept (replyRecord -> {
529
+ Observation .Context observationContext = observationRegistry .getCurrentObservation ().getContext ();
530
+ assertThat (observationContext ).isInstanceOf (KafkaRecordReceiverContext .class );
531
+ replyObservationContext .set ((KafkaRecordReceiverContext ) observationContext );
532
+ });
533
+ Awaitility .await ().atMost (Duration .ofSeconds (60 )).until (() ->
534
+ replyObservationContext .get () != null && "spring.kafka.listener" .equals (replyObservationContext .get ().getName ()));
535
+ }
536
+
514
537
@ Configuration
515
538
@ EnableKafka
516
539
public static class Config {
@@ -584,13 +607,22 @@ KafkaTemplate<Integer, String> reuseAdminBeanKafkaTemplate(
584
607
return template ;
585
608
}
586
609
610
+ @ Bean
611
+ ReplyingKafkaTemplate <Integer , String , String > replyingKafkaTemplate (ProducerFactory <Integer , String > pf , ConcurrentKafkaListenerContainerFactory <Integer , String > containerFactory ) {
612
+ ReplyingKafkaTemplate <Integer , String , String > kafkaTemplate = new ReplyingKafkaTemplate <>(pf , containerFactory .createContainer (OBSERVATION_REPLY ));
613
+ kafkaTemplate .setObservationEnabled (true );
614
+ return kafkaTemplate ;
615
+ }
616
+
587
617
@ Bean
588
618
ConcurrentKafkaListenerContainerFactory <Integer , String > kafkaListenerContainerFactory (
589
- ConsumerFactory <Integer , String > cf , ObservationRegistry observationRegistry ) {
619
+ ConsumerFactory <Integer , String > cf , ObservationRegistry observationRegistry ,
620
+ KafkaTemplate <Integer , String > kafkaTemplate ) {
590
621
591
622
ConcurrentKafkaListenerContainerFactory <Integer , String > factory =
592
623
new ConcurrentKafkaListenerContainerFactory <>();
593
624
factory .setConsumerFactory (cf );
625
+ factory .setReplyTemplate (kafkaTemplate );
594
626
factory .getContainerProperties ().setObservationEnabled (true );
595
627
factory .setContainerCustomizer (container -> {
596
628
if (container .getListenerId ().equals ("obs3" )) {
@@ -721,6 +753,11 @@ void listen2(ConsumerRecord<?, ?> in) {
721
753
void listen3 (ConsumerRecord <Integer , String > in ) {
722
754
}
723
755
756
+ @ KafkaListener (id = "obsReply" , topics = OBSERVATION_TEST_4 )
757
+ @ SendTo // default REPLY_TOPIC header
758
+ public String replyListener (ConsumerRecord <Integer , String > in ) {
759
+ return in .value ().toUpperCase ();
760
+ }
724
761
}
725
762
726
763
public static class ExceptionListener {
0 commit comments