Skip to content

Commit b212b4d

Browse files
authored
GH-2817: KafkaTemplate: No double error timers (#2823)
* GH-2817: KafkaTemplate: No double error timers Fixes #2817 An observation in `KafkaTemplate` can be marked with error from a `Callback`. Then `Future` is evaluated and its exception is thrown back to the `observeSend()`. Here this exception is caught and reported to the observation again. This creates a second timer in the Micrometer, but with different error tag * Check for error presence in the `observeSend()` `catch` block and skip second report **Cherry-pick to `3.0.x`** * * Fix import order for Checkstyle rule
1 parent 1957a8c commit b212b4d

File tree

2 files changed

+21
-6
lines changed

2 files changed

+21
-6
lines changed

spring-kafka/src/main/java/org/springframework/kafka/core/KafkaTemplate.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -756,8 +756,11 @@ private CompletableFuture<SendResult<K, V>> observeSend(final ProducerRecord<K,
756756
return doSend(producerRecord, observation);
757757
}
758758
catch (RuntimeException ex) {
759-
observation.error(ex);
760-
observation.stop();
759+
// The error is added from org.apache.kafka.clients.producer.Callback
760+
if (observation.getContext().getError() == null) {
761+
observation.error(ex);
762+
observation.stop();
763+
}
761764
throw ex;
762765
}
763766
}
@@ -783,7 +786,7 @@ protected CompletableFuture<SendResult<K, V>> doSend(final ProducerRecord<K, V>
783786
}
784787
Future<RecordMetadata> sendFuture =
785788
producer.send(producerRecord, buildCallback(producerRecord, producer, future, sample, observation));
786-
// May be an immediate failure
789+
// Maybe an immediate failure
787790
if (sendFuture.isDone()) {
788791
try {
789792
sendFuture.get();

spring-kafka/src/test/java/org/springframework/kafka/support/micrometer/ObservationTests.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.kafka.support.micrometer;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2021
import static org.awaitility.Awaitility.await;
2122
import static org.mockito.Mockito.mock;
2223

@@ -33,6 +34,7 @@
3334
import org.apache.kafka.clients.consumer.ConsumerConfig;
3435
import org.apache.kafka.clients.consumer.ConsumerRecord;
3536
import org.apache.kafka.clients.producer.ProducerConfig;
37+
import org.apache.kafka.common.errors.InvalidTopicException;
3638
import org.apache.kafka.common.header.Headers;
3739
import org.junit.jupiter.api.Test;
3840

@@ -41,6 +43,7 @@
4143
import org.springframework.context.annotation.Bean;
4244
import org.springframework.context.annotation.Configuration;
4345
import org.springframework.context.annotation.Primary;
46+
import org.springframework.kafka.KafkaException;
4447
import org.springframework.kafka.annotation.EnableKafka;
4548
import org.springframework.kafka.annotation.KafkaListener;
4649
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
@@ -81,8 +84,9 @@
8184

8285
/**
8386
* @author Gary Russell
84-
* @since 3.0
87+
* @author Artem Bilan
8588
*
89+
* @since 3.0
8690
*/
8791
@SpringJUnitConfig
8892
@EmbeddedKafka(topics = { "observation.testT1", "observation.testT2", "ObservationTests.testT3" })
@@ -216,6 +220,14 @@ public KeyValues getLowCardinalityKeyValues(KafkaRecordReceiverContext context)
216220
.getPropertyValue(endpointRegistry.getListenerContainer("obs3"), "containers", List.class).get(0);
217221
cAdmin = KafkaTestUtils.getPropertyValue(container, "listenerConsumer.kafkaAdmin", KafkaAdmin.class);
218222
assertThat(cAdmin).isSameAs(config.mockAdmin);
223+
224+
assertThatExceptionOfType(KafkaException.class)
225+
.isThrownBy(() -> template.send("wrong%Topic", "data"))
226+
.withCauseExactlyInstanceOf(InvalidTopicException.class);
227+
228+
MeterRegistryAssert.assertThat(meterRegistry)
229+
.hasTimerWithNameAndTags("spring.kafka.template", KeyValues.of("error", "InvalidTopicException"))
230+
.doesNotHaveMeterWithNameAndTags("spring.kafka.template", KeyValues.of("error", "KafkaException"));
219231
}
220232

221233
@Configuration
@@ -235,15 +247,15 @@ KafkaAdmin admin(EmbeddedKafkaBroker broker) {
235247
@Bean
236248
ProducerFactory<Integer, String> producerFactory(EmbeddedKafkaBroker broker) {
237249
Map<String, Object> producerProps = KafkaTestUtils.producerProps(broker);
238-
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, broker.getBrokersAsString() + ","
250+
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, broker.getBrokersAsString() + ","
239251
+ broker.getBrokersAsString());
240252
return new DefaultKafkaProducerFactory<>(producerProps);
241253
}
242254

243255
@Bean
244256
ConsumerFactory<Integer, String> consumerFactory(EmbeddedKafkaBroker broker) {
245257
Map<String, Object> consumerProps = KafkaTestUtils.consumerProps("obs", "false", broker);
246-
consumerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, broker.getBrokersAsString() + ","
258+
consumerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, broker.getBrokersAsString() + ","
247259
+ broker.getBrokersAsString() + "," + broker.getBrokersAsString());
248260
return new DefaultKafkaConsumerFactory<>(consumerProps);
249261
}

0 commit comments

Comments
 (0)