Skip to content

Commit ccdd5d2

Browse files
committed
Add observability adapter for Lettuce.
We now provide MicrometerTracingAdapter to connect Lettuce to Micrometer Tracing. Original pull request: spring-projects#2439 Closes spring-projects#2348
1 parent 190b28c commit ccdd5d2

File tree

13 files changed

+1081
-1
lines changed

13 files changed

+1081
-1
lines changed

pom.xml

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
34

45
<modelVersion>4.0.0</modelVersion>
56

@@ -28,6 +29,11 @@
2829
<multithreadedtc>1.01</multithreadedtc>
2930
<netty>4.1.79.Final</netty>
3031
<java-module-name>spring.data.redis</java-module-name>
32+
33+
<!-- Observability -->
34+
<micrometer-docs-generator.inputPath>${project.basedir}</micrometer-docs-generator.inputPath>
35+
<micrometer-docs-generator.inclusionPattern>.*</micrometer-docs-generator.inclusionPattern>
36+
<micrometer-docs-generator.outputPath>${project.basedir}/target/</micrometer-docs-generator.outputPath>
3137
</properties>
3238

3339
<scm>
@@ -157,6 +163,44 @@
157163
<optional>true</optional>
158164
</dependency>
159165

166+
<!-- Observability -->
167+
168+
<dependency>
169+
<groupId>io.micrometer</groupId>
170+
<artifactId>micrometer-observation</artifactId>
171+
<optional>true</optional>
172+
</dependency>
173+
174+
<dependency>
175+
<groupId>io.micrometer</groupId>
176+
<artifactId>micrometer-tracing</artifactId>
177+
<optional>true</optional>
178+
</dependency>
179+
180+
<dependency>
181+
<groupId>io.micrometer</groupId>
182+
<artifactId>micrometer-test</artifactId>
183+
<scope>test</scope>
184+
<exclusions>
185+
<exclusion>
186+
<groupId>com.github.tomakehurst</groupId>
187+
<artifactId>wiremock-jre8-standalone</artifactId>
188+
</exclusion>
189+
</exclusions>
190+
</dependency>
191+
192+
<dependency>
193+
<groupId>io.micrometer</groupId>
194+
<artifactId>micrometer-tracing-test</artifactId>
195+
<scope>test</scope>
196+
</dependency>
197+
198+
<dependency>
199+
<groupId>io.micrometer</groupId>
200+
<artifactId>micrometer-tracing-integration-test</artifactId>
201+
<scope>test</scope>
202+
</dependency>
203+
160204
<!-- CDI -->
161205
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
162206

@@ -285,6 +329,38 @@
285329
</configuration>
286330
</plugin>
287331

332+
<plugin>
333+
<groupId>org.codehaus.mojo</groupId>
334+
<artifactId>exec-maven-plugin</artifactId>
335+
<version>3.1.0</version>
336+
<executions>
337+
<execution>
338+
<id>generate-docs</id>
339+
<phase>generate-resources</phase>
340+
<goals>
341+
<goal>java</goal>
342+
</goals>
343+
<configuration>
344+
<mainClass>io.micrometer.docs.DocsGeneratorCommand</mainClass>
345+
<includePluginDependencies>true</includePluginDependencies>
346+
<arguments>
347+
<argument>${micrometer-docs-generator.inputPath}</argument>
348+
<argument>${micrometer-docs-generator.inclusionPattern}</argument>
349+
<argument>${micrometer-docs-generator.outputPath}</argument>
350+
</arguments>
351+
</configuration>
352+
</execution>
353+
</executions>
354+
<dependencies>
355+
<dependency>
356+
<groupId>io.micrometer</groupId>
357+
<artifactId>micrometer-docs-generator</artifactId>
358+
<version>${micrometer-docs-generator}</version>
359+
<type>jar</type>
360+
</dependency>
361+
</dependencies>
362+
</plugin>
363+
288364
<plugin>
289365
<groupId>org.apache.maven.plugins</groupId>
290366
<artifactId>maven-assembly-plugin</artifactId>

src/main/asciidoc/index.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ include::{spring-data-commons-docs}/dependencies.adoc[leveloffset=+1]
2828
include::reference/introduction.adoc[leveloffset=+1]
2929
include::reference/why-sdr.adoc[leveloffset=+1]
3030
include::reference/redis.adoc[leveloffset=+1]
31+
include::reference/observability.adoc[leveloffset=+1]
3132
include::reference/reactive-redis.adoc[leveloffset=+1]
3233
include::reference/redis-cluster.adoc[leveloffset=+1]
3334
include::reference/redis-repositories.adoc[leveloffset=+1]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[[redis.observability]]
2+
== Observability
3+
4+
Getting insights from an application component about its operations, timing and relation to application code is crucial to understand latency.
5+
Spring Data Redis ships with a Micrometer integration through the Lettuce driver to collect observations during Redis interaction.
6+
Once the integration is set up, Micrometer will create meters and spans (for distributed tracing) for each Redis command.
7+
8+
To enable the integration, apply the following configuration to `LettuceClientConfiguration`:
9+
10+
[source,java]
11+
----
12+
@Configuration
13+
class ObservabilityConfiguration {
14+
15+
@Bean
16+
public ClientResources clientResources(ObservationRegistry observationRegistry) {
17+
18+
return ClientResources.builder()
19+
.tracing(new MicrometerTracingAdapter(observationRegistry, "my-redis-cache"))
20+
.build();
21+
}
22+
23+
@Bean
24+
public LettuceConnectionFactory lettuceConnectionFactory(ClientResources clientResources) {
25+
26+
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
27+
.clientResources(clientResources).build();
28+
RedisConfiguration redisConfiguration = …;
29+
return new LettuceConnectionFactory(redisConfiguration, clientConfig);
30+
}
31+
}
32+
----
33+
34+
include::../../../../target/_conventions.adoc[]
35+
36+
include::../../../../target/_metrics.adoc[]
37+
38+
include::../../../../target/_spans.adoc[]
39+
40+
See also https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/database/#redis[OpenTelemetry Semantic Conventions] for further reference.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.redis.connection.lettuce.observability;
17+
18+
import java.net.InetSocketAddress;
19+
import java.util.Locale;
20+
21+
import org.springframework.data.redis.connection.lettuce.observability.RedisObservation.HighCardinalityCommandKeyNames;
22+
import org.springframework.data.redis.connection.lettuce.observability.RedisObservation.LowCardinalityCommandKeyNames;
23+
24+
import io.lettuce.core.protocol.RedisCommand;
25+
import io.lettuce.core.tracing.Tracing.Endpoint;
26+
import io.micrometer.common.KeyValues;
27+
28+
/**
29+
* Default {@link LettuceObservationConvention} implementation.
30+
*
31+
* @author Mark Paluch
32+
* @since 3.0
33+
*/
34+
record DefaultLettuceObservationConvention(
35+
boolean includeCommandArgsInSpanTags) implements LettuceObservationConvention {
36+
37+
@Override
38+
public KeyValues getLowCardinalityKeyValues(LettuceObservationContext context) {
39+
40+
Endpoint ep = context.getRequiredEndpoint();
41+
KeyValues keyValues = KeyValues.of(LowCardinalityCommandKeyNames.DATABASE_SYSTEM.withValue("redis"), //
42+
LowCardinalityCommandKeyNames.REDIS_COMMAND.withValue(context.getRequiredCommand().getType().name()));
43+
44+
if (ep instanceof SocketAddressEndpoint endpoint) {
45+
46+
if (endpoint.socketAddress()instanceof InetSocketAddress inet) {
47+
keyValues = keyValues
48+
.and(KeyValues.of(LowCardinalityCommandKeyNames.NET_SOCK_PEER_ADDR.withValue(inet.getHostString()),
49+
LowCardinalityCommandKeyNames.NET_SOCK_PEER_PORT.withValue("" + inet.getPort()),
50+
LowCardinalityCommandKeyNames.NET_TRANSPORT.withValue("IP.TCP")));
51+
} else {
52+
keyValues = keyValues
53+
.and(KeyValues.of(LowCardinalityCommandKeyNames.NET_PEER_NAME.withValue(endpoint.toString()),
54+
LowCardinalityCommandKeyNames.NET_TRANSPORT.withValue("Unix")));
55+
}
56+
}
57+
58+
return keyValues;
59+
}
60+
61+
@Override
62+
public KeyValues getHighCardinalityKeyValues(LettuceObservationContext context) {
63+
64+
RedisCommand<?, ?, ?> command = context.getRequiredCommand();
65+
66+
if (includeCommandArgsInSpanTags) {
67+
68+
if (command.getArgs() != null) {
69+
return KeyValues.of(HighCardinalityCommandKeyNames.STATEMENT
70+
.withValue(command.getType().name() + " " + command.getArgs().toCommandString()));
71+
}
72+
}
73+
74+
return KeyValues.empty();
75+
}
76+
77+
@Override
78+
public String getContextualName(LettuceObservationContext context) {
79+
return context.getRequiredCommand().getType().name().toLowerCase(Locale.ROOT);
80+
}
81+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.redis.connection.lettuce.observability;
17+
18+
import org.springframework.lang.Nullable;
19+
20+
import io.lettuce.core.protocol.RedisCommand;
21+
import io.lettuce.core.tracing.Tracing.Endpoint;
22+
import io.micrometer.observation.Observation;
23+
import io.micrometer.observation.transport.Kind;
24+
import io.micrometer.observation.transport.SenderContext;
25+
26+
/**
27+
* Micrometer {@link Observation.Context} holding Lettuce contextual details.
28+
*
29+
* @author Mark Paluch
30+
* @since 3.0
31+
*/
32+
class LettuceObservationContext extends SenderContext<Object> {
33+
34+
private volatile @Nullable RedisCommand<?, ?, ?> command;
35+
36+
private volatile @Nullable Endpoint endpoint;
37+
38+
public LettuceObservationContext(String serviceName) {
39+
super((carrier, key, value) -> {}, Kind.CLIENT);
40+
setRemoteServiceName(serviceName);
41+
}
42+
43+
public RedisCommand<?, ?, ?> getRequiredCommand() {
44+
45+
RedisCommand<?, ?, ?> local = command;
46+
47+
if (local == null) {
48+
throw new IllegalArgumentException("LettuceObservationContext is not associated with a Command");
49+
}
50+
51+
return local;
52+
}
53+
54+
public void setCommand(RedisCommand<?, ?, ?> command) {
55+
this.command = command;
56+
}
57+
58+
public Endpoint getRequiredEndpoint() {
59+
60+
Endpoint local = endpoint;
61+
62+
if (local == null) {
63+
throw new IllegalArgumentException("LettuceObservationContext is not associated with a Endpoint");
64+
}
65+
66+
return local;
67+
}
68+
69+
public void setEndpoint(Endpoint endpoint) {
70+
this.endpoint = endpoint;
71+
}
72+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.redis.connection.lettuce.observability;
17+
18+
import io.micrometer.observation.Observation;
19+
import io.micrometer.observation.ObservationConvention;
20+
21+
/**
22+
* {@link ObservationConvention} for {@link LettuceObservationContext}.
23+
*
24+
* @author Mark Paluch
25+
* @since 3.0
26+
*/
27+
interface LettuceObservationConvention extends ObservationConvention<LettuceObservationContext> {
28+
29+
@Override
30+
default boolean supportsContext(Observation.Context context) {
31+
return context instanceof LettuceObservationContext;
32+
}
33+
34+
}

0 commit comments

Comments
 (0)