Skip to content

Commit d4619f0

Browse files
garyrussellartembilan
authored andcommitted
GH-2198: Observability Documentation
Resolves #2417 Refactor for latest snapshots; add documentation generation. Use NOOP registry and supplier for context. GH-2417: Fix class tangles. Disable auto doc generation and polish manually. Remove unnecessary `Supplier` casts. Remove incorrect `@Nullable`. Add Remote Service Name to Contexts Include the cluster id if possible. * Move deps to the latest snapshots
1 parent 239e350 commit d4619f0

18 files changed

+348
-146
lines changed

build.gradle

+44-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
buildscript {
22
ext.kotlinVersion = '1.7.0'
3+
ext.isCI = System.getenv('GITHUB_ACTION') || System.getenv('bamboo_buildKey')
34
repositories {
45
mavenCentral()
56
gradlePluginPortal()
@@ -66,15 +67,16 @@ ext {
6667
junitJupiterVersion = '5.9.0'
6768
kafkaVersion = '3.3.1'
6869
log4jVersion = '2.18.0'
69-
micrometerVersion = '1.10.0-M6'
70-
micrometerTracingVersion = '1.0.0-M8'
70+
micrometerDocsVersion = "1.0.0-SNAPSHOT"
71+
micrometerVersion = '1.10.0-SNAPSHOT'
72+
micrometerTracingVersion = '1.0.0-SNAPSHOT'
7173
mockitoVersion = '4.5.1'
72-
reactorVersion = '2022.0.0-M6'
74+
reactorVersion = '2022.0.0-SNAPSHOT'
7375
scalaVersion = '2.13'
7476
springBootVersion = '2.6.11' // docs module
75-
springDataVersion = '2022.0.0-M6'
76-
springRetryVersion = '2.0.0-M1'
77-
springVersion = '6.0.0-M6'
77+
springDataVersion = '2022.0.0-SNAPSHOT'
78+
springRetryVersion = '2.0.0-SNAPSHOT'
79+
springVersion = '6.0.0-SNAPSHOT'
7880
zookeeperVersion = '3.6.3'
7981

8082
idPrefix = 'kafka'
@@ -241,7 +243,7 @@ subprojects { subproject ->
241243
}
242244

243245
task updateCopyrights {
244-
onlyIf { gitPresent && !System.getenv('GITHUB_ACTION') && !System.getenv('bamboo_buildKey') }
246+
onlyIf { !isCI }
245247
if (gitPresent) {
246248
inputs.files(modifiedFiles.filter { f -> f.path.contains(subproject.name) })
247249
}
@@ -300,13 +302,17 @@ subprojects { subproject ->
300302
tasks.withType(Javadoc) {
301303
options.addBooleanOption('Xdoclint:syntax', true) // only check syntax with doclint
302304
options.addBooleanOption('Werror', true) // fail build on Javadoc warnings
303-
}
305+
}
304306

305307
}
306308

307309
project ('spring-kafka') {
308310
description = 'Spring Kafka Support'
309311

312+
configurations {
313+
adoc
314+
}
315+
310316
dependencies {
311317
api 'org.springframework:spring-context'
312318
api 'org.springframework:spring-messaging'
@@ -346,7 +352,36 @@ project ('spring-kafka') {
346352
testImplementation 'io.micrometer:micrometer-tracing-bridge-brave'
347353
testImplementation 'io.micrometer:micrometer-tracing-test'
348354
testImplementation 'io.micrometer:micrometer-tracing-integration-test'
355+
356+
adoc "io.micrometer:micrometer-docs-generator-spans:$micrometerDocsVersion"
357+
adoc "io.micrometer:micrometer-docs-generator-metrics:$micrometerDocsVersion"
349358
}
359+
360+
def inputDir = file('src/main/java/org/springframework/kafka/support/micrometer').absolutePath
361+
def outputDir = rootProject.file('spring-kafka-docs/src/main/asciidoc').absolutePath
362+
363+
task generateObservabilityMetricsDocs(type: JavaExec) {
364+
onlyIf { !isCI }
365+
mainClass = 'io.micrometer.docs.metrics.DocsFromSources'
366+
inputs.dir(inputDir)
367+
outputs.dir(outputDir)
368+
classpath configurations.adoc
369+
args inputDir, '.*', outputDir
370+
}
371+
372+
task generateObservabilitySpansDocs(type: JavaExec) {
373+
onlyIf { !isCI }
374+
mainClass = 'io.micrometer.docs.spans.DocsFromSources'
375+
inputs.dir(inputDir)
376+
outputs.dir(outputDir)
377+
classpath configurations.adoc
378+
args inputDir, '.*', outputDir
379+
}
380+
381+
// javadoc {
382+
// finalizedBy generateObservabilityMetricsDocs, generateObservabilitySpansDocs
383+
// }
384+
350385
}
351386

352387
project ('spring-kafka-test') {
@@ -564,7 +599,7 @@ task distZip(type: Zip, dependsOn: [docsZip]) { //, schemaZip]) {
564599
into "${baseDir}"
565600
}
566601

567-
from("$project.rootDir") {
602+
from("$project.rootDir") {
568603
include 'LICENSE.txt'
569604
into "${baseDir}"
570605
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[[observability-conventions]]
2+
=== Observability - Conventions
3+
4+
Below you can find a list of all `GlobalObservabilityConventions` and `ObservabilityConventions` declared by this project.
5+
6+
.ObservationConvention implementations
7+
|===
8+
|ObservationConvention Class Name | Applicable ObservationContext Class Name
9+
|`KafkaListenerObservation$DefaultKafkaListenerObservationConvention`|`KafkaRecordReceiverContext`
10+
|`KafkaTemplateObservation$DefaultKafkaTemplateObservationConvention`|`KafkaRecordSenderContext`
11+
|===
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
[[observability-metrics]]
2+
=== Observability - Metrics
3+
4+
Below you can find a list of all samples declared by this project.
5+
6+
[[observability-metrics-listener-observation]]
7+
==== Listener Observation
8+
9+
____
10+
Observation for Apache Kafka listeners.
11+
____
12+
13+
**Metric name** `spring.kafka.listener` (defined by convention class `KafkaListenerObservation$DefaultKafkaListenerObservationConvention`). **Type** `timer` and **base unit** `seconds`.
14+
15+
Name of the enclosing class `KafkaListenerObservation`.
16+
17+
IMPORTANT: All tags must be prefixed with `spring.kafka.listener` prefix!
18+
19+
.Low cardinality Keys
20+
[cols="a,a"]
21+
|===
22+
|Name | Description
23+
|`spring.kafka.listener.id`|Listener id (or listener container bean name).
24+
|===
25+
26+
[[observability-metrics-template-observation]]
27+
==== Template Observation
28+
29+
____
30+
Observation for KafkaTemplates.
31+
____
32+
33+
**Metric name** `spring.kafka.template` (defined by convention class `KafkaTemplateObservation$DefaultKafkaTemplateObservationConvention`). **Type** `timer` and **base unit** `seconds`.
34+
35+
Name of the enclosing class `KafkaTemplateObservation`.
36+
37+
IMPORTANT: All tags must be prefixed with `spring.kafka.template` prefix!
38+
39+
.Low cardinality Keys
40+
[cols="a,a"]
41+
|===
42+
|Name | Description
43+
|`spring.kafka.template.name`|Bean name of the template.
44+
|===
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[[observability-spans]]
2+
=== Observability - Spans
3+
4+
Below you can find a list of all spans declared by this project.
5+
6+
[[observability-spans-listener-observation]]
7+
==== Listener Observation Span
8+
9+
> Observation for Apache Kafka listeners.
10+
11+
**Span name** `spring.kafka.listener` (defined by convention class `KafkaListenerObservation$DefaultKafkaListenerObservationConvention`).
12+
13+
Name of the enclosing class `KafkaListenerObservation`.
14+
15+
IMPORTANT: All tags and event names must be prefixed with `spring.kafka.listener` prefix!
16+
17+
.Tag Keys
18+
|===
19+
|Name | Description
20+
|`spring.kafka.listener.id`|Listener id (or listener container bean name).
21+
|===
22+
23+
[[observability-spans-template-observation]]
24+
==== Template Observation Span
25+
26+
> Observation for KafkaTemplates.
27+
28+
**Span name** `spring.kafka.template` (defined by convention class `KafkaTemplateObservation$DefaultKafkaTemplateObservationConvention`).
29+
30+
Name of the enclosing class `KafkaTemplateObservation`.
31+
32+
IMPORTANT: All tags and event names must be prefixed with `spring.kafka.template` prefix!
33+
34+
.Tag Keys
35+
|===
36+
|Name | Description
37+
|`spring.kafka.template.name`|Bean name of the template.
38+
|===

spring-kafka-docs/src/main/asciidoc/appendix.adoc

+10
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ dependencies {
4949

5050
The test scope dependencies are only needed if you are using the embedded Kafka broker in tests.
5151

52+
[appendix]
53+
[[observation-gen]]
54+
== Micrometer Observation Documentation
55+
56+
include::./_metrics.adoc[]
57+
58+
include::./_spans.adoc[]
59+
60+
include::./_conventions.adoc[]
61+
5262
[appendix]
5363
[[history]]
5464
== Change History

spring-kafka-docs/src/main/asciidoc/kafka.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -3415,6 +3415,8 @@ The default implementations add the `bean.name` tag for template observations an
34153415

34163416
You can either subclass `DefaultKafkaTemplateObservationConvention` or `DefaultKafkaListenerObservationConvention` or provide completely new implementations.
34173417

3418+
See <<observation-gen>> for details of the observations that are recorded.
3419+
34183420
[[transactions]]
34193421
==== Transactions
34203422

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

+21
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.springframework.context.ApplicationContextAware;
5858
import org.springframework.core.log.LogAccessor;
5959
import org.springframework.kafka.KafkaException;
60+
import org.springframework.lang.Nullable;
6061

6162
/**
6263
* An admin that delegates to an {@link AdminClient} to create topics defined
@@ -95,6 +96,8 @@ public class KafkaAdmin extends KafkaResourceFactory
9596

9697
private boolean modifyTopicConfigs;
9798

99+
private String clusterId;
100+
98101
/**
99102
* Create an instance with an {@link AdminClient} based on the supplied
100103
* configuration.
@@ -197,6 +200,10 @@ public final boolean initialize() {
197200
}
198201
if (adminClient != null) {
199202
try {
203+
synchronized (this) {
204+
this.clusterId = adminClient.describeCluster().clusterId().get(this.operationTimeout,
205+
TimeUnit.MILLISECONDS);
206+
}
200207
addOrModifyTopicsIfNeeded(adminClient, newTopics);
201208
return true;
202209
}
@@ -218,6 +225,20 @@ public final boolean initialize() {
218225
return false;
219226
}
220227

228+
@Override
229+
@Nullable
230+
public String clusterId() {
231+
if (this.clusterId == null) {
232+
try (AdminClient client = createAdmin()) {
233+
this.clusterId = client.describeCluster().clusterId().get(this.operationTimeout, TimeUnit.MILLISECONDS);
234+
}
235+
catch (Exception ex) {
236+
LOGGER.error(ex, "Could not obtaine cluster info");
237+
}
238+
}
239+
return this.clusterId;
240+
}
241+
221242
@Override
222243
public void createOrModifyTopics(NewTopic... topics) {
223244
try (AdminClient client = createAdmin()) {

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 the original author or authors.
2+
* Copyright 2021-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@
2121
import org.apache.kafka.clients.admin.NewTopic;
2222
import org.apache.kafka.clients.admin.TopicDescription;
2323

24+
import org.springframework.lang.Nullable;
25+
2426
/**
2527
* Provides a number of convenience methods wrapping {@code AdminClient}.
2628
*
@@ -49,4 +51,12 @@ public interface KafkaAdminOperations {
4951
*/
5052
Map<String, TopicDescription> describeTopics(String... topicNames);
5153

54+
/**
55+
* Return the cluster id, if available.
56+
* @return the describe cluster id.
57+
* @since 3.0
58+
*/
59+
@Nullable
60+
String clusterId();
61+
5262
}

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

+24-17
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848

4949
import org.springframework.beans.factory.BeanNameAware;
5050
import org.springframework.beans.factory.DisposableBean;
51-
import org.springframework.beans.factory.ObjectProvider;
5251
import org.springframework.beans.factory.SmartInitializingSingleton;
5352
import org.springframework.context.ApplicationContext;
5453
import org.springframework.context.ApplicationContextAware;
@@ -64,9 +63,9 @@
6463
import org.springframework.kafka.support.TopicPartitionOffset;
6564
import org.springframework.kafka.support.converter.MessagingMessageConverter;
6665
import org.springframework.kafka.support.converter.RecordMessageConverter;
67-
import org.springframework.kafka.support.micrometer.DefaultKafkaTemplateObservationConvention;
6866
import org.springframework.kafka.support.micrometer.KafkaRecordSenderContext;
6967
import org.springframework.kafka.support.micrometer.KafkaTemplateObservation;
68+
import org.springframework.kafka.support.micrometer.KafkaTemplateObservation.DefaultKafkaTemplateObservationConvention;
7069
import org.springframework.kafka.support.micrometer.KafkaTemplateObservationConvention;
7170
import org.springframework.kafka.support.micrometer.MicrometerHolder;
7271
import org.springframework.lang.Nullable;
@@ -145,7 +144,11 @@ public class KafkaTemplate<K, V> implements KafkaOperations<K, V>, ApplicationCo
145144

146145
private KafkaTemplateObservationConvention observationConvention;
147146

148-
private ObservationRegistry observationRegistry;
147+
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
148+
149+
private KafkaAdmin kafkaAdmin;
150+
151+
private String clusterId;
149152

150153
/**
151154
* Create an instance using the supplied producer factory and autoFlush false.
@@ -418,16 +421,25 @@ public void setObservationConvention(KafkaTemplateObservationConvention observat
418421

419422
@Override
420423
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();
424+
if (this.observationEnabled && this.applicationContext != null) {
425+
this.observationRegistry = this.applicationContext.getBeanProvider(ObservationRegistry.class).getIfUnique();
426+
this.kafkaAdmin = this.applicationContext.getBeanProvider(KafkaAdmin.class).getIfUnique();
427+
if (this.kafkaAdmin != null) {
428+
this.clusterId = this.kafkaAdmin.clusterId();
429+
}
425430
}
426431
else if (this.micrometerEnabled) {
427432
this.micrometerHolder = obtainMicrometerHolder();
428433
}
429434
}
430435

436+
private String clusterId() {
437+
if (this.kafkaAdmin != null && this.clusterId == null) {
438+
this.clusterId = this.kafkaAdmin.clusterId();
439+
}
440+
return this.clusterId;
441+
}
442+
431443
@Override
432444
public void onApplicationEvent(ContextStoppedEvent event) {
433445
if (this.customProducerFactory) {
@@ -668,15 +680,10 @@ protected void closeProducer(Producer<K, V> producer, boolean inTx) {
668680
}
669681

670682
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-
}
683+
Observation observation = KafkaTemplateObservation.TEMPLATE_OBSERVATION.observation(
684+
this.observationConvention, DefaultKafkaTemplateObservationConvention.INSTANCE,
685+
() -> new KafkaRecordSenderContext(producerRecord, this.beanName, this::clusterId),
686+
this.observationRegistry);
680687
try {
681688
observation.start();
682689
return doSend(producerRecord, observation);
@@ -695,7 +702,7 @@ private CompletableFuture<SendResult<K, V>> observeSend(final ProducerRecord<K,
695702
* RecordMetadata}.
696703
*/
697704
protected CompletableFuture<SendResult<K, V>> doSend(final ProducerRecord<K, V> producerRecord,
698-
@Nullable Observation observation) {
705+
Observation observation) {
699706

700707
final Producer<K, V> producer = getTheProducer(producerRecord.topic());
701708
this.logger.trace(() -> "Sending: " + KafkaUtils.format(producerRecord));

0 commit comments

Comments
 (0)