Skip to content

Commit c8a78da

Browse files
committed
spring-projectsGH-2198: Spring Observability Initial Commit
Resolves spring-projects#2198
1 parent f1253d4 commit c8a78da

13 files changed

+888
-45
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,14 @@ project ('spring-kafka') {
335335
optionalApi 'io.projectreactor:reactor-core'
336336
optionalApi 'io.projectreactor.kafka:reactor-kafka'
337337
optionalApi 'io.micrometer:micrometer-core'
338+
api 'io.micrometer:micrometer-observation'
338339
optionalApi 'io.micrometer:micrometer-tracing'
339340

340341
testImplementation project (':spring-kafka-test')
341342
testImplementation 'io.projectreactor:reactor-test'
342343
testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion"
343344
testImplementation "org.hibernate.validator:hibernate-validator:$hibernateValidationVersion"
345+
testImplementation 'io.micrometer:micrometer-observation-test'
344346
testImplementation 'io.micrometer:micrometer-tracing-bridge-brave'
345347
testImplementation 'io.micrometer:micrometer-tracing-test'
346348
testImplementation 'io.micrometer:micrometer-tracing-integration-test'

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

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848

4949
import org.springframework.beans.factory.BeanNameAware;
5050
import org.springframework.beans.factory.DisposableBean;
51+
import org.springframework.beans.factory.ObjectProvider;
52+
import org.springframework.beans.factory.SmartInitializingSingleton;
5153
import org.springframework.context.ApplicationContext;
5254
import org.springframework.context.ApplicationContextAware;
5355
import org.springframework.context.ApplicationListener;
@@ -62,13 +64,20 @@
6264
import org.springframework.kafka.support.TopicPartitionOffset;
6365
import org.springframework.kafka.support.converter.MessagingMessageConverter;
6466
import org.springframework.kafka.support.converter.RecordMessageConverter;
67+
import org.springframework.kafka.support.micrometer.DefaultKafkaTemplateObservationConvention;
68+
import org.springframework.kafka.support.micrometer.KafkaRecordSenderContext;
69+
import org.springframework.kafka.support.micrometer.KafkaTemplateObservation;
70+
import org.springframework.kafka.support.micrometer.KafkaTemplateObservationConvention;
6571
import org.springframework.kafka.support.micrometer.MicrometerHolder;
6672
import org.springframework.lang.Nullable;
6773
import org.springframework.messaging.Message;
6874
import org.springframework.messaging.converter.SmartMessageConverter;
6975
import org.springframework.transaction.support.TransactionSynchronizationManager;
7076
import org.springframework.util.Assert;
7177

78+
import io.micrometer.observation.Observation;
79+
import io.micrometer.observation.ObservationRegistry;
80+
7281
/**
7382
* A template for executing high-level operations. When used with a
7483
* {@link DefaultKafkaProducerFactory}, the template is thread-safe. The producer factory
@@ -90,7 +99,7 @@
9099
*/
91100
@SuppressWarnings("deprecation")
92101
public class KafkaTemplate<K, V> implements KafkaOperations<K, V>, ApplicationContextAware, BeanNameAware,
93-
ApplicationListener<ContextStoppedEvent>, DisposableBean {
102+
ApplicationListener<ContextStoppedEvent>, DisposableBean, SmartInitializingSingleton {
94103

95104
protected final LogAccessor logger = new LogAccessor(LogFactory.getLog(this.getClass())); //NOSONAR
96105

@@ -126,11 +135,17 @@ public class KafkaTemplate<K, V> implements KafkaOperations<K, V>, ApplicationCo
126135

127136
private ConsumerFactory<K, V> consumerFactory;
128137

129-
private volatile boolean micrometerEnabled = true;
138+
private ProducerInterceptor<K, V> producerInterceptor;
139+
140+
private boolean micrometerEnabled = true;
130141

131-
private volatile MicrometerHolder micrometerHolder;
142+
private MicrometerHolder micrometerHolder;
132143

133-
private ProducerInterceptor<K, V> producerInterceptor;
144+
private boolean observationEnabled;
145+
146+
private KafkaTemplateObservationConvention observationConvention;
147+
148+
private ObservationRegistry observationRegistry;
134149

135150
/**
136151
* Create an instance using the supplied producer factory and autoFlush false.
@@ -382,6 +397,37 @@ public void setProducerInterceptor(ProducerInterceptor<K, V> producerInterceptor
382397
this.producerInterceptor = producerInterceptor;
383398
}
384399

400+
/**
401+
* Set to true to enable observation via Micrometer.
402+
* @param observationEnabled true to enable.
403+
* @since 3.0
404+
* @see #setMicrometerEnabled(boolean)
405+
*/
406+
public void setObservationEnabled(boolean observationEnabled) {
407+
this.observationEnabled = observationEnabled;
408+
}
409+
410+
/**
411+
* Set a custom {@link KafkaTemplateObservationConvention}.
412+
* @param observationConvention the convention.
413+
* @since 3.0
414+
*/
415+
public void setObservationConvention(KafkaTemplateObservationConvention observationConvention) {
416+
this.observationConvention = observationConvention;
417+
}
418+
419+
@Override
420+
public void afterSingletonsInstantiated() {
421+
if (this.observationEnabled && this.observationRegistry == null && this.applicationContext != null) {
422+
ObjectProvider<ObservationRegistry> registry =
423+
this.applicationContext.getBeanProvider(ObservationRegistry.class);
424+
this.observationRegistry = registry.getIfUnique();
425+
}
426+
else if (this.micrometerEnabled) {
427+
this.micrometerHolder = obtainMicrometerHolder();
428+
}
429+
}
430+
385431
@Override
386432
public void onApplicationEvent(ContextStoppedEvent event) {
387433
if (this.customProducerFactory) {
@@ -412,33 +458,33 @@ public CompletableFuture<SendResult<K, V>> sendDefault(Integer partition, Long t
412458
@Override
413459
public CompletableFuture<SendResult<K, V>> send(String topic, @Nullable V data) {
414460
ProducerRecord<K, V> producerRecord = new ProducerRecord<>(topic, data);
415-
return doSend(producerRecord);
461+
return observeSend(producerRecord);
416462
}
417463

418464
@Override
419465
public CompletableFuture<SendResult<K, V>> send(String topic, K key, @Nullable V data) {
420466
ProducerRecord<K, V> producerRecord = new ProducerRecord<>(topic, key, data);
421-
return doSend(producerRecord);
467+
return observeSend(producerRecord);
422468
}
423469

424470
@Override
425471
public CompletableFuture<SendResult<K, V>> send(String topic, Integer partition, K key, @Nullable V data) {
426472
ProducerRecord<K, V> producerRecord = new ProducerRecord<>(topic, partition, key, data);
427-
return doSend(producerRecord);
473+
return observeSend(producerRecord);
428474
}
429475

430476
@Override
431477
public CompletableFuture<SendResult<K, V>> send(String topic, Integer partition, Long timestamp, K key,
432478
@Nullable V data) {
433479

434480
ProducerRecord<K, V> producerRecord = new ProducerRecord<>(topic, partition, timestamp, key, data);
435-
return doSend(producerRecord);
481+
return observeSend(producerRecord);
436482
}
437483

438484
@Override
439485
public CompletableFuture<SendResult<K, V>> send(ProducerRecord<K, V> record) {
440486
Assert.notNull(record, "'record' cannot be null");
441-
return doSend(record);
487+
return observeSend(record);
442488
}
443489

444490
@SuppressWarnings("unchecked")
@@ -451,7 +497,7 @@ public CompletableFuture<SendResult<K, V>> send(Message<?> message) {
451497
producerRecord.headers().add(KafkaHeaders.CORRELATION_ID, correlationId);
452498
}
453499
}
454-
return doSend((ProducerRecord<K, V>) producerRecord);
500+
return observeSend((ProducerRecord<K, V>) producerRecord);
455501
}
456502

457503

@@ -621,6 +667,18 @@ protected void closeProducer(Producer<K, V> producer, boolean inTx) {
621667
}
622668
}
623669

670+
private CompletableFuture<SendResult<K, V>> observeSend(final ProducerRecord<K, V> producerRecord) {
671+
Observation observation;
672+
if (!this.observationEnabled || this.observationRegistry == null) {
673+
observation = Observation.NOOP;
674+
}
675+
else {
676+
observation = KafkaTemplateObservation.TEMPLATE_OBSERVATION.observation(
677+
this.observationConvention, DefaultKafkaTemplateObservationConvention.INSTANCE,
678+
new KafkaRecordSenderContext(producerRecord, this.beanName), this.observationRegistry);
679+
}
680+
return observation.observe(() -> doSend(producerRecord));
681+
}
624682
/**
625683
* Send the producer record.
626684
* @param producerRecord the producer record.
@@ -632,9 +690,6 @@ protected CompletableFuture<SendResult<K, V>> doSend(final ProducerRecord<K, V>
632690
this.logger.trace(() -> "Sending: " + KafkaUtils.format(producerRecord));
633691
final CompletableFuture<SendResult<K, V>> future = new CompletableFuture<>();
634692
Object sample = null;
635-
if (this.micrometerEnabled && this.micrometerHolder == null) {
636-
this.micrometerHolder = obtainMicrometerHolder();
637-
}
638693
if (this.micrometerHolder != null) {
639694
sample = this.micrometerHolder.start();
640695
}

spring-kafka/src/main/java/org/springframework/kafka/listener/ContainerProperties.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.aop.support.AopUtils;
3333
import org.springframework.core.task.AsyncTaskExecutor;
3434
import org.springframework.kafka.support.TopicPartitionOffset;
35+
import org.springframework.kafka.support.micrometer.KafkaListenerObservationConvention;
3536
import org.springframework.lang.Nullable;
3637
import org.springframework.scheduling.TaskScheduler;
3738
import org.springframework.transaction.PlatformTransactionManager;
@@ -262,6 +263,8 @@ public enum EOSMode {
262263

263264
private boolean micrometerEnabled = true;
264265

266+
private boolean observationEnabled;
267+
265268
private Duration consumerStartTimeout = DEFAULT_CONSUMER_START_TIMEOUT;
266269

267270
private Boolean subBatchPerPartition;
@@ -282,6 +285,8 @@ public enum EOSMode {
282285

283286
private boolean pauseImmediate;
284287

288+
private KafkaListenerObservationConvention observationConvention;
289+
285290
/**
286291
* Create properties for a container that will subscribe to the specified topics.
287292
* @param topics the topics.
@@ -635,13 +640,28 @@ public boolean isMicrometerEnabled() {
635640

636641
/**
637642
* Set to false to disable the Micrometer listener timers. Default true.
643+
* Disabled when {@link #setObservationEnabled(boolean)} is true.
638644
* @param micrometerEnabled false to disable.
639645
* @since 2.3
640646
*/
641647
public void setMicrometerEnabled(boolean micrometerEnabled) {
642648
this.micrometerEnabled = micrometerEnabled;
643649
}
644650

651+
public boolean isObservationEnabled() {
652+
return this.observationEnabled;
653+
}
654+
655+
/**
656+
* Set to true to enable observation via Micrometer.
657+
* @param observationEnabled true to enable.
658+
* @since 3.0
659+
* @see #setMicrometerEnabled(boolean)
660+
*/
661+
public void setObservationEnabled(boolean observationEnabled) {
662+
this.observationEnabled = observationEnabled;
663+
}
664+
645665
/**
646666
* Set additional tags for the Micrometer listener timers.
647667
* @param tags the tags.
@@ -912,6 +932,19 @@ private void adviseListenerIfNeeded() {
912932
}
913933
}
914934

935+
public KafkaListenerObservationConvention getObservationConvention() {
936+
return this.observationConvention;
937+
}
938+
939+
/**
940+
* Set a custom {@link KafkaListenerObservationConvention}.
941+
* @param observationConvention the convention.
942+
* @since 3.0
943+
*/
944+
public void setObservationConvention(KafkaListenerObservationConvention observationConvention) {
945+
this.observationConvention = observationConvention;
946+
}
947+
915948
@Override
916949
public String toString() {
917950
return "ContainerProperties ["
@@ -942,7 +975,12 @@ public String toString() {
942975
+ "\n stopContainerWhenFenced=" + this.stopContainerWhenFenced
943976
+ "\n stopImmediate=" + this.stopImmediate
944977
+ "\n asyncAcks=" + this.asyncAcks
945-
+ "\n idleBeforeDataMultiplier" + this.idleBeforeDataMultiplier
978+
+ "\n idleBeforeDataMultiplier=" + this.idleBeforeDataMultiplier
979+
+ "\n micrometerEnabled=" + this.micrometerEnabled
980+
+ "\n observationEnabled=" + this.observationEnabled
981+
+ (this.observationConvention != null
982+
? "\n observationConvention=" + this.observationConvention
983+
: "")
946984
+ "\n]";
947985
}
948986

0 commit comments

Comments
 (0)