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 ;
81
82
import org .springframework .kafka .core .ProducerFactory ;
82
83
import org .springframework .kafka .listener .MessageListenerContainer ;
83
84
import org .springframework .kafka .listener .RecordInterceptor ;
85
+ import org .springframework .kafka .requestreply .ReplyingKafkaTemplate ;
84
86
import org .springframework .kafka .support .ProducerListener ;
85
87
import org .springframework .kafka .support .micrometer .KafkaListenerObservation .DefaultKafkaListenerObservationConvention ;
86
88
import org .springframework .kafka .support .micrometer .KafkaTemplateObservation .DefaultKafkaTemplateObservationConvention ;
87
89
import org .springframework .kafka .test .EmbeddedKafkaBroker ;
88
90
import org .springframework .kafka .test .context .EmbeddedKafka ;
89
91
import org .springframework .kafka .test .utils .KafkaTestUtils ;
92
+ import org .springframework .messaging .handler .annotation .SendTo ;
90
93
import org .springframework .test .annotation .DirtiesContext ;
91
94
import org .springframework .test .context .junit .jupiter .SpringJUnitConfig ;
92
95
import org .springframework .util .StringUtils ;
102
105
* @author Wang Zhiyang
103
106
* @author Christian Mergenthaler
104
107
* @author Soby Chacko
108
+ * @author Francois Rosiere
105
109
*
106
110
* @since 3.0
107
111
*/
108
112
@ SpringJUnitConfig
109
- @ 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 )
113
+ @ EmbeddedKafka (topics = {ObservationTests .OBSERVATION_TEST_1 , ObservationTests .OBSERVATION_TEST_2 ,
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" ;
@@ -135,11 +144,12 @@ void endToEnd(@Autowired Listener listener, @Autowired KafkaTemplate<Integer, St
135
144
@ Autowired KafkaListenerEndpointRegistry endpointRegistry , @ Autowired KafkaAdmin admin ,
136
145
@ Autowired @ Qualifier ("customTemplate" ) KafkaTemplate <Integer , String > customTemplate ,
137
146
@ Autowired Config config )
138
- throws InterruptedException , ExecutionException , TimeoutException {
147
+ throws InterruptedException , ExecutionException , TimeoutException {
139
148
140
149
AtomicReference <SimpleSpan > spanFromCallback = new AtomicReference <>();
141
150
142
151
template .setProducerInterceptor (new ProducerInterceptor <>() {
152
+
143
153
@ Override
144
154
public ProducerRecord <Integer , String > onSend (ProducerRecord <Integer , String > record ) {
145
155
tracer .currentSpanCustomizer ().tag ("key" , "value" );
@@ -327,10 +337,10 @@ private void assertThatTemplateHasTimerWithNameAndTags(MeterRegistryAssert meter
327
337
328
338
meterRegistryAssert .hasTimerWithNameAndTags ("spring.kafka.template" ,
329
339
KeyValues .of ("spring.kafka.template.name" , "template" ,
330
- "messaging.operation" , "publish" ,
331
- "messaging.system" , "kafka" ,
332
- "messaging.destination.kind" , "topic" ,
333
- "messaging.destination.name" , destName )
340
+ "messaging.operation" , "publish" ,
341
+ "messaging.system" , "kafka" ,
342
+ "messaging.destination.kind" , "topic" ,
343
+ "messaging.destination.name" , destName )
334
344
.and (keyValues ));
335
345
}
336
346
@@ -339,12 +349,12 @@ private void assertThatListenerHasTimerWithNameAndTags(MeterRegistryAssert meter
339
349
340
350
meterRegistryAssert .hasTimerWithNameAndTags ("spring.kafka.listener" ,
341
351
KeyValues .of (
342
- "messaging.kafka.consumer.group" , consumerGroup ,
343
- "messaging.operation" , "receive" ,
344
- "messaging.source.kind" , "topic" ,
345
- "messaging.source.name" , destName ,
346
- "messaging.system" , "kafka" ,
347
- "spring.kafka.listener.id" , listenerId )
352
+ "messaging.kafka.consumer.group" , consumerGroup ,
353
+ "messaging.operation" , "receive" ,
354
+ "messaging.source.kind" , "topic" ,
355
+ "messaging.source.name" , destName ,
356
+ "messaging.system" , "kafka" ,
357
+ "spring.kafka.listener.id" , listenerId )
348
358
.and (keyValues ));
349
359
}
350
360
@@ -394,7 +404,7 @@ void observationRuntimeException(@Autowired ExceptionListener listener, @Autowir
394
404
void observationErrorException (@ Autowired ExceptionListener listener , @ Autowired SimpleTracer tracer ,
395
405
@ Autowired @ Qualifier ("throwableTemplate" ) KafkaTemplate <Integer , String > errorTemplate ,
396
406
@ Autowired KafkaListenerEndpointRegistry endpointRegistry )
397
- throws ExecutionException , InterruptedException , TimeoutException {
407
+ throws ExecutionException , InterruptedException , TimeoutException {
398
408
399
409
errorTemplate .send (OBSERVATION_ERROR , "testError" ).get (10 , TimeUnit .SECONDS );
400
410
assertThat (listener .latch5 .await (10 , TimeUnit .SECONDS )).isTrue ();
@@ -485,6 +495,7 @@ void verifyTraceParentHeader(@Autowired KafkaTemplate<Integer, String> template,
485
495
@ Autowired SimpleTracer tracer ) throws Exception {
486
496
CompletableFuture <ProducerRecord <Integer , String >> producerRecordFuture = new CompletableFuture <>();
487
497
template .setProducerListener (new ProducerListener <>() {
498
+
488
499
@ Override
489
500
public void onSuccess (ProducerRecord <Integer , String > producerRecord , RecordMetadata recordMetadata ) {
490
501
producerRecordFuture .complete (producerRecord );
@@ -511,6 +522,18 @@ public void onSuccess(ProducerRecord<Integer, String> producerRecord, RecordMeta
511
522
tracer .getSpans ().clear ();
512
523
}
513
524
525
+ @ Test
526
+ void testReplyingKafkaTemplateObservation (
527
+ @ Autowired ReplyingKafkaTemplate <Integer , String , String > template ,
528
+ @ Autowired ObservationRegistry observationRegistry ) {
529
+ assertThat (template .sendAndReceive (new ProducerRecord <>(OBSERVATION_TEST_4 , "test" ))
530
+ // the current observation must be retrieved from the consumer thread of the reply
531
+ .thenApply (replyRecord -> observationRegistry .getCurrentObservation ().getContext ()))
532
+ .isCompletedWithValueMatchingWithin (observationContext ->
533
+ observationContext instanceof KafkaRecordReceiverContext
534
+ && "spring.kafka.listener" .equals (observationContext .getName ()), Duration .ofSeconds (30 ));
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,12 @@ 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
+ }
761
+
724
762
}
725
763
726
764
public static class ExceptionListener {
0 commit comments