Skip to content

Commit f24fbd9

Browse files
authored
Add documentation for Observability (#3896)
* Add documentation for Observability * Adapt Observation code to the latest dependencies * Add doc generation tasks for meters and spans * Document new Observation API features * Include generated meters and spans docs to a general `metrics.adoc` chapter * * Adapt `ObservationPropagationChannelInterceptorTests` for the latest `SpansAssert` API * * Adjust to the latest Micrometer SNAPSHOT * Make Observation doc generation tasks only as local. We don't need ambiguous changes to source code on CI * * Automate metrics/spans docs generation as a part of `reference` build phase * Replace 'org.springframework.integration' content in the generated files with a 'o.s.i' to make it easier to read, especially in the tables * Break `DefaultMessageReceiverObservationConvention <=> IntegrationObservation` classes tangle using literal for `KeyValues` in the `DefaultMessageReceiverObservationConvention` instead of nested enums from the `IntegrationObservation` * Some other minor build script clean up * Fix indent in `build.gradle` for `micrometerVersion` property code line * Add new line after observation section in whats-new.adoc * * Adapt to the latest Micrometer changes * * Use Reactor `2022.0.0-SNAPSHOT` version
1 parent a667171 commit f24fbd9

File tree

10 files changed

+122
-56
lines changed

10 files changed

+122
-56
lines changed

build.gradle

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ buildscript {
55
mavenCentral()
66
gradlePluginPortal()
77
maven { url 'https://repo.spring.io/plugins-release-local' }
8+
if (version.endsWith('SNAPSHOT')) {
9+
maven { url 'https://repo.spring.io/snapshot' }
10+
}
811
}
912
dependencies {
1013
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
@@ -84,15 +87,15 @@ ext {
8487
lettuceVersion = '6.2.0.RELEASE'
8588
log4jVersion = '2.19.0'
8689
mailVersion = '2.0.1'
87-
micrometerVersion = '1.10.0-M6'
88-
micrometerTracingVersion = '1.0.0-M8'
90+
micrometerVersion = '1.10.0-SNAPSHOT'
91+
micrometerTracingVersion = '1.0.0-SNAPSHOT'
8992
mockitoVersion = '4.8.0'
9093
mongoDriverVersion = '4.7.1'
9194
mysqlVersion = '8.0.30'
9295
pahoMqttClientVersion = '1.2.5'
9396
postgresVersion = '42.5.0'
9497
r2dbch2Version = '1.0.0.RC1'
95-
reactorVersion = '2022.0.0-M6'
98+
reactorVersion = '2022.0.0-SNAPSHOT'
9699
resilience4jVersion = '1.7.1'
97100
romeToolsVersion = '1.18.0'
98101
rsocketVersion = '1.1.3'
@@ -534,7 +537,6 @@ project('spring-integration-core') {
534537
exclude group: 'io.opentelemetry'
535538
exclude group: 'com.wavefront'
536539
exclude group: 'io.micrometer', module: 'micrometer-tracing-bridge-otel'
537-
538540
}
539541
}
540542

gradle/docs.gradle

+63-26
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
ext {
22
backendVersion = '0.0.3'
3+
micrometerDocsVersion='1.0.0-SNAPSHOT'
34
}
45

56
configurations {
67
asciidoctorExtensions
8+
micrometerDocs
79
}
810

911
dependencies {
1012
asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:$backendVersion"
13+
micrometerDocs "io.micrometer:micrometer-docs-generator-spans:$micrometerDocsVersion"
14+
micrometerDocs "io.micrometer:micrometer-docs-generator-metrics:$micrometerDocsVersion"
1115
}
1216

1317
task checkAsciidocLinks {
14-
inputs.dir("src/reference/asciidoc/")
18+
inputs.dir('src/reference/asciidoc')
1519
doLast {
1620
def errors = new ArrayList<>();
1721
errors.add('*** Anchor reference errors found:')
@@ -48,8 +52,42 @@ task checkAsciidocLinks {
4852
}
4953
}
5054

55+
def observationInputDir = file('spring-integration-core/src/main/java/org/springframework/integration/support/management/observation').absolutePath
56+
def generatedDocsDir = file("$buildDir/docs/generated").absolutePath
57+
58+
task generateObservabilityMetricsDocs(type: JavaExec) {
59+
inputs.dir(observationInputDir)
60+
outputs.dir(generatedDocsDir)
61+
classpath configurations.micrometerDocs
62+
args observationInputDir, /.+/, generatedDocsDir
63+
mainClass = 'io.micrometer.docs.metrics.DocsFromSources'
64+
}
65+
66+
task generateObservabilitySpansDocs(type: JavaExec) {
67+
inputs.dir(observationInputDir)
68+
outputs.dir(generatedDocsDir)
69+
classpath configurations.micrometerDocs
70+
args observationInputDir, /.+/, generatedDocsDir
71+
mainClass = 'io.micrometer.docs.spans.DocsFromSources'
72+
}
73+
74+
task filterMetricsDocsContent(type: Copy) {
75+
dependsOn generateObservabilitySpansDocs, generateObservabilityMetricsDocs
76+
from generatedDocsDir
77+
include '_*.adoc'
78+
into generatedDocsDir
79+
rename { filename -> filename.replace '_', '' }
80+
filter { line -> line.replaceAll('org.springframework.integration', 'o.s.i') }
81+
}
82+
83+
task prepareDocs(type: Copy) {
84+
dependsOn checkAsciidocLinks, filterMetricsDocsContent
85+
from 'src/reference/asciidoc'
86+
into "$buildDir/docs"
87+
}
88+
5189
asciidoctorPdf {
52-
dependsOn checkAsciidocLinks
90+
dependsOn prepareDocs
5391

5492
inProcess = JAVA_EXEC
5593
forkOptions {
@@ -59,7 +97,7 @@ asciidoctorPdf {
5997
baseDirFollowsSourceFile()
6098

6199
asciidoctorj {
62-
sourceDir "src/reference/asciidoc/"
100+
sourceDir "$buildDir/docs"
63101
inputs.dir(sourceDir)
64102
sources {
65103
include 'index-single.adoc'
@@ -75,8 +113,28 @@ asciidoctorPdf {
75113
}
76114
}
77115

78-
asciidoctorj {
79-
version = '2.5.2'
116+
asciidoctor {
117+
dependsOn asciidoctorPdf
118+
119+
inProcess = JAVA_EXEC
120+
forkOptions {
121+
jvmArgs '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', '--add-opens', 'java.base/java.io=ALL-UNNAMED'
122+
}
123+
124+
baseDirFollowsSourceFile()
125+
126+
configurations 'asciidoctorExtensions'
127+
sourceDir "$buildDir/docs"
128+
inputs.dir(sourceDir)
129+
outputOptions {
130+
backends 'spring-html'
131+
}
132+
resources {
133+
from(sourceDir) {
134+
include 'images/*', 'css/**', 'js/**'
135+
}
136+
}
137+
80138
options doctype: 'book', eruby: 'erubis'
81139
attributes 'docinfo': 'shared',
82140
stylesdir: 'css/',
@@ -97,27 +155,6 @@ asciidoctorj {
97155
'project-version': project.version
98156
}
99157

100-
asciidoctor {
101-
dependsOn asciidoctorPdf
102-
inProcess = JAVA_EXEC
103-
forkOptions {
104-
jvmArgs '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', '--add-opens', 'java.base/java.io=ALL-UNNAMED'
105-
}
106-
baseDirFollowsSourceFile()
107-
configurations 'asciidoctorExtensions'
108-
sourceDir "src/reference/asciidoc/"
109-
inputs.dir(sourceDir)
110-
outputOptions {
111-
backends "spring-html"
112-
}
113-
resources {
114-
from(sourceDir) {
115-
include 'images/*', 'css/**', 'js/**'
116-
}
117-
}
118-
119-
}
120-
121158
task reference(dependsOn: asciidoctor) {
122159
group = 'Documentation'
123160
description = 'Generate the reference documentation'

spring-integration-core/src/main/java/org/springframework/integration/handler/AbstractMessageHandler.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private void handleWithObservation(Message<?> message, ObservationRegistry obser
8181
IntegrationObservation.HANDLER.observation(
8282
this.observationConvention,
8383
DefaultMessageReceiverObservationConvention.INSTANCE,
84-
new MessageReceiverContext(message, getComponentName()),
84+
() -> new MessageReceiverContext(message, getComponentName()),
8585
observationRegistry)
8686
.observe(() -> doHandleMessage(message));
8787
}

spring-integration-core/src/main/java/org/springframework/integration/support/management/observation/DefaultMessageReceiverObservationConvention.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ public class DefaultMessageReceiverObservationConvention implements MessageRecei
3636

3737
@Override
3838
public KeyValues getLowCardinalityKeyValues(MessageReceiverContext context) {
39-
return KeyValues.of(
40-
IntegrationObservation.HandlerTags.COMPONENT_NAME.withValue(context.getHandlerName()),
41-
IntegrationObservation.HandlerTags.COMPONENT_TYPE.withValue("handler"));
39+
return KeyValues
40+
// See IntegrationObservation.HandlerTags.COMPONENT_NAME - to avoid class tangle
41+
.of("spring.integration.name", context.getHandlerName())
42+
// See IntegrationObservation.HandlerTags.COMPONENT_TYPE - to avoid class tangle
43+
.and("spring.integration.type", "handler");
4244
}
4345

4446
}

spring-integration-core/src/main/java/org/springframework/integration/support/management/observation/IntegrationObservation.java

+3-8
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,21 @@
1717
package org.springframework.integration.support.management.observation;
1818

1919
import io.micrometer.common.docs.KeyName;
20-
import io.micrometer.observation.docs.DocumentedObservation;
20+
import io.micrometer.observation.docs.ObservationDocumentation;
2121

2222
/**
23-
* The {@link DocumentedObservation} implementation for Spring Integration infrastructure.
23+
* The {@link ObservationDocumentation} implementation for Spring Integration infrastructure.
2424
*
2525
* @author Artem Bilan
2626
*
2727
* @since 6.0
2828
*/
29-
public enum IntegrationObservation implements DocumentedObservation {
29+
public enum IntegrationObservation implements ObservationDocumentation {
3030

3131
/**
3232
* Observation for message handlers.
3333
*/
3434
HANDLER {
35-
@Override
36-
public String getName() {
37-
return "spring.integration.handler";
38-
}
39-
4035
@Override
4136
public String getPrefix() {
4237
return "spring.integration.";

spring-integration-core/src/main/java/org/springframework/integration/support/management/observation/MessageReceiverObservationConvention.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@
2929
*
3030
* @since 6.0
3131
*/
32-
public interface MessageReceiverObservationConvention
33-
extends ObservationConvention<MessageReceiverContext> {
32+
public interface MessageReceiverObservationConvention extends ObservationConvention<MessageReceiverContext> {
33+
34+
@Override
35+
default String getName() {
36+
return "spring.integration.handler";
37+
}
3438

3539
@Override
3640
default boolean supportsContext(Observation.Context context) {

spring-integration-core/src/test/java/org/springframework/integration/channel/interceptor/ObservationPropagationChannelInterceptorTests.java

+2-9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static org.assertj.core.api.Assertions.assertThat;
2020

2121
import java.util.Arrays;
22-
import java.util.Collection;
2322
import java.util.List;
2423
import java.util.concurrent.CountDownLatch;
2524
import java.util.concurrent.Executors;
@@ -67,7 +66,6 @@
6766
import io.micrometer.tracing.Span;
6867
import io.micrometer.tracing.TraceContext;
6968
import io.micrometer.tracing.Tracer;
70-
import io.micrometer.tracing.exporter.FinishedSpan;
7169
import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
7270
import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler;
7371
import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler;
@@ -223,7 +221,7 @@ void observationContextPropagatedOverExecutorChannel() {
223221
.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel)
224222
.build();
225223

226-
Observation.createNotStarted("sending", new MessageSenderContext(message), this.observationRegistry)
224+
Observation.createNotStarted("sending", () -> new MessageSenderContext(message), this.observationRegistry)
227225
.observe(() -> this.testTracingChannel.send(message));
228226

229227
Message<?> receive = replyChannel.receive();
@@ -240,7 +238,7 @@ void observationContextPropagatedOverExecutorChannel() {
240238
TracerAssert.assertThat(this.simpleTracer)
241239
.reportedSpans()
242240
.hasSize(2)
243-
.satisfies(simpleSpans -> assertSpans(simpleSpans)
241+
.satisfies(simpleSpans -> SpansAssert.assertThat(simpleSpans)
244242
.hasASpanWithName("sending")
245243
.assertThatASpanWithNameEqualTo("testBridge receive")
246244
.hasTag("foo", "some foo value")
@@ -259,11 +257,6 @@ void observationContextPropagatedOverExecutorChannel() {
259257
assertThat(this.meterRegistry.get("spring.integration.handler").timer().count()).isEqualTo(1);
260258
}
261259

262-
@SuppressWarnings("unchecked")
263-
private static SpansAssert assertSpans(Collection<? extends FinishedSpan> actual) {
264-
return SpansAssert.assertThat((Collection<FinishedSpan>) actual);
265-
}
266-
267260
@Configuration
268261
@EnableIntegration
269262
public static class ContextConfiguration {

spring-integration-core/src/test/java/org/springframework/integration/support/management/observation/IntegrationObservabilityZipkinTests.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ public SampleTestRunnerConsumer yourCode() {
7373
PollableChannel queueChannel = applicationContext.getBean("queueChannel", PollableChannel.class);
7474
PollableChannel replyChannel = new QueueChannel();
7575

76-
MutableMessage<String> testMessage =
76+
MutableMessage<String> message =
7777
(MutableMessage<String>) MutableMessageBuilder.withPayload("test data")
7878
.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel)
7979
.build();
8080

81-
Observation.createNotStarted("Test send", new MessageSenderContext(testMessage), observationRegistry)
82-
.observe(() -> queueChannel.send(testMessage));
81+
Observation.createNotStarted("Test send", () -> new MessageSenderContext(message), observationRegistry)
82+
.observe(() -> queueChannel.send(message));
8383

8484
Message<?> receive = replyChannel.receive(10_000);
8585
assertThat(receive).isNotNull()

src/reference/asciidoc/metrics.adoc

+26
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,32 @@ registry.config().meterFilter(MeterFilter.deny(id ->
145145
----
146146
====
147147

148+
[[micrometer-observation]]
149+
==== Micrometer Observation
150+
151+
Starting with version 6.0, Spring Integration utilizes a Micrometer Observation abstraction which can handle metrics as well as https://micrometer.io/docs/tracing[tracing] via appropriate `ObservationHandler` configuration.
152+
153+
The observation handling is enabled on the `IntegrationManagement` components whenever an `ObservationRegistry` bean is present in the application context.
154+
The meters are not gathered in this case independently, but delegated to an appropriate `ObservationHandler` configured on the provided `ObservationRegistry`.
155+
156+
An observation production on the `IntegrationManagement` components can be customized via `ObservationConvention` configuration.
157+
For example an `AbstractMessageHandler` expects a `MessageReceiverObservationConvention` via its `setObservationConvention()` API.
158+
159+
The following are supported metrics, spans and conventions for Observation API:
160+
161+
include::./generated/metrics.adoc[leveloffset=+2]
162+
163+
include::./generated/spans.adoc[leveloffset=+2]
164+
165+
include::./generated/conventions.adoc[leveloffset=+2]
166+
167+
==== Observation Propagation
168+
169+
To supply a connected chain of spans in one trace, independently of the nature of the messaging flow, Spring Integration provides an `ObservationPropagationChannelInterceptor` implementation.
170+
This can be configured on `MessageChannnel` beans individually or as a `@GlobalChannelInterceptor` with respective `MessageChannnel` bean names pattern matching.
171+
The goal of this interceptor is to propagate an `Observation` from the producer thread to the consumer one independently of the `MessageChannnel` implementation and nature.
172+
A `DirectChannel`, though, is ignored since its consumer is executed directly on the producer thread.
173+
148174
==== Spring Integration JMX Support
149175

150176
Also see <<./jmx.adoc#jmx,JMX Support>>.

src/reference/asciidoc/whats-new.adoc

+7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ See <<./amqp.adoc#rmq-streams,RabbitMQ Stream Queue Support>> for more informati
5656
The SFTP modules has been fully reworked from outdated JCraft JSch library to more robust and modern `org.apache.sshd:sshd-sftp` module of the Apache MINA project.
5757

5858
See <<./sftp.adoc#sftp,SFTP Adapters>> for more information.
59+
60+
[[x6.0-micrometer-observation]]
61+
==== Micrometer Observation
62+
63+
Enabling observation for timers and tracing using Micrometer is now supported.
64+
See <<./metrics.adoc#micrometer-observation,Micrometer Observation>> for more information.
65+
5966
[[x6.0-general]]
6067
=== General Changes
6168

0 commit comments

Comments
 (0)