Skip to content

Commit 3c34a81

Browse files
christophstroblgregturn
authored andcommitted
Introduce Observability with Micrometer and Micrometer Tracing.
See #3942.
1 parent a3f782c commit 3c34a81

File tree

14 files changed

+1349
-0
lines changed

14 files changed

+1349
-0
lines changed

spring-data-mongodb-distribution/pom.xml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
<properties>
2222
<project.root>${basedir}/..</project.root>
2323
<dist.key>SDMONGO</dist.key>
24+
25+
<!-- Observability -->
26+
<micrometer-docs-generator.version>1.0.0-M1</micrometer-docs-generator.version>
27+
<micrometer-docs-generator.inputPath>${maven.multiModuleProjectDirectory}/spring-data-mongodb/</micrometer-docs-generator.inputPath>
28+
<micrometer-docs-generator.inclusionPattern>.*</micrometer-docs-generator.inclusionPattern>
29+
<micrometer-docs-generator.outputPath>${maven.multiModuleProjectDirectory}/target/</micrometer-docs-generator.outputPath>
2430
</properties>
2531

2632
<build>
@@ -29,6 +35,58 @@
2935
<groupId>org.apache.maven.plugins</groupId>
3036
<artifactId>maven-assembly-plugin</artifactId>
3137
</plugin>
38+
<plugin>
39+
<groupId>org.codehaus.mojo</groupId>
40+
<artifactId>exec-maven-plugin</artifactId>
41+
<executions>
42+
<execution>
43+
<id>generate-metrics-metadata</id>
44+
<phase>prepare-package</phase>
45+
<goals>
46+
<goal>java</goal>
47+
</goals>
48+
<configuration>
49+
<mainClass>io.micrometer.docs.metrics.DocsFromSources</mainClass>
50+
</configuration>
51+
</execution>
52+
<execution>
53+
<id>generate-tracing-metadata</id>
54+
<phase>prepare-package</phase>
55+
<goals>
56+
<goal>java</goal>
57+
</goals>
58+
<configuration>
59+
<mainClass>io.micrometer.docs.spans.DocsFromSources</mainClass>
60+
</configuration>
61+
</execution>
62+
</executions>
63+
<dependencies>
64+
<dependency>
65+
<groupId>io.micrometer
66+
</groupId>
67+
<artifactId>micrometer-docs-generator-spans</artifactId>
68+
<version>${micrometer-docs-generator.version}
69+
</version>
70+
<type>jar</type>
71+
</dependency>
72+
<dependency>
73+
<groupId>io.micrometer
74+
</groupId>
75+
<artifactId>micrometer-docs-generator-metrics</artifactId>
76+
<version>${micrometer-docs-generator.version}
77+
</version>
78+
<type>jar</type>
79+
</dependency>
80+
</dependencies>
81+
<configuration>
82+
<includePluginDependencies>true</includePluginDependencies>
83+
<arguments>
84+
<argument>${micrometer-docs-generator.inputPath}</argument>
85+
<argument>${micrometer-docs-generator.inclusionPattern}</argument>
86+
<argument>${micrometer-docs-generator.outputPath}</argument>
87+
</arguments>
88+
</configuration>
89+
</plugin>
3290
<plugin>
3391
<groupId>org.asciidoctor</groupId>
3492
<artifactId>asciidoctor-maven-plugin</artifactId>

spring-data-mongodb/pom.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@
193193
<optional>true</optional>
194194
</dependency>
195195

196+
<dependency>
197+
<groupId>io.micrometer</groupId>
198+
<artifactId>micrometer-tracing-api</artifactId>
199+
<optional>true</optional>
200+
</dependency>
201+
196202
<dependency>
197203
<groupId>org.hibernate</groupId>
198204
<artifactId>hibernate-validator</artifactId>
@@ -295,6 +301,23 @@
295301
<scope>test</scope>
296302
</dependency>
297303

304+
<dependency>
305+
<groupId>io.micrometer</groupId>
306+
<artifactId>micrometer-test</artifactId>
307+
<scope>test</scope>
308+
</dependency>
309+
<dependency>
310+
<groupId>io.micrometer</groupId>
311+
<artifactId>micrometer-tracing-test</artifactId>
312+
<scope>test</scope>
313+
</dependency>
314+
315+
<dependency>
316+
<groupId>io.micrometer</groupId>
317+
<artifactId>micrometer-tracing-integration-test</artifactId>
318+
<scope>test</scope>
319+
</dependency>
320+
298321
<!-- jMolecules -->
299322

300323
<dependency>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2013-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.mongodb.observability;
17+
18+
import io.micrometer.core.instrument.Tag;
19+
import io.micrometer.core.instrument.Tags;
20+
21+
import com.mongodb.connection.ConnectionDescription;
22+
import com.mongodb.connection.ConnectionId;
23+
import com.mongodb.event.CommandStartedEvent;
24+
25+
/**
26+
* Default {@link MongoHandlerTagsProvider} implementation.
27+
*
28+
* @author Greg Turnquist
29+
* @since 4.0.0
30+
*/
31+
public class DefaultMongoHandlerTagsProvider implements MongoHandlerTagsProvider {
32+
33+
@Override
34+
public Tags getLowCardinalityTags(MongoHandlerContext context) {
35+
36+
Tags tags = Tags.empty();
37+
38+
if (context.getCollectionName() != null) {
39+
tags = tags.and(MongoObservation.LowCardinalityCommandTags.MONGODB_COLLECTION.of(context.getCollectionName()));
40+
}
41+
42+
Tag connectionTag = connectionTag(context.getCommandStartedEvent());
43+
if (connectionTag != null) {
44+
tags = tags.and(connectionTag);
45+
}
46+
47+
return tags;
48+
}
49+
50+
@Override
51+
public Tags getHighCardinalityTags(MongoHandlerContext context) {
52+
return Tags.of(MongoObservation.HighCardinalityCommandTags.MONGODB_COMMAND
53+
.of(context.getCommandStartedEvent().getCommandName()));
54+
}
55+
56+
/**
57+
* Extract connection details for a MongoDB connection into a {@link Tag}.
58+
*
59+
* @param event
60+
* @return
61+
*/
62+
private static Tag connectionTag(CommandStartedEvent event) {
63+
64+
ConnectionDescription connectionDescription = event.getConnectionDescription();
65+
66+
if (connectionDescription != null) {
67+
68+
ConnectionId connectionId = connectionDescription.getConnectionId();
69+
if (connectionId != null) {
70+
return MongoObservation.LowCardinalityCommandTags.MONGODB_CLUSTER_ID
71+
.of(connectionId.getServerId().getClusterId().getValue());
72+
}
73+
}
74+
75+
return null;
76+
}
77+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2013-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.mongodb.observability;
17+
18+
import io.micrometer.core.instrument.observation.Observation;
19+
20+
import java.util.Arrays;
21+
import java.util.LinkedHashSet;
22+
import java.util.Set;
23+
24+
import org.bson.BsonDocument;
25+
import org.bson.BsonValue;
26+
import org.springframework.lang.Nullable;
27+
28+
import com.mongodb.RequestContext;
29+
import com.mongodb.event.CommandFailedEvent;
30+
import com.mongodb.event.CommandStartedEvent;
31+
import com.mongodb.event.CommandSucceededEvent;
32+
33+
/**
34+
* A {@link Observation.Context} that contains MongoDB events.
35+
*
36+
* @author Marcin Grzejszczak
37+
* @author Greg Turnquist
38+
* @since 4.0.0
39+
*/
40+
public class MongoHandlerContext extends Observation.Context {
41+
42+
/**
43+
* @see https://docs.mongodb.com/manual/reference/command for the command reference
44+
*/
45+
private static final Set<String> COMMANDS_WITH_COLLECTION_NAME = new LinkedHashSet<>(
46+
Arrays.asList("aggregate", "count", "distinct", "mapReduce", "geoSearch", "delete", "find", "findAndModify",
47+
"insert", "update", "collMod", "compact", "convertToCapped", "create", "createIndexes", "drop", "dropIndexes",
48+
"killCursors", "listIndexes", "reIndex"));
49+
50+
private final CommandStartedEvent commandStartedEvent;
51+
private final RequestContext requestContext;
52+
private final String collectionName;
53+
54+
private CommandSucceededEvent commandSucceededEvent;
55+
private CommandFailedEvent commandFailedEvent;
56+
57+
public MongoHandlerContext(CommandStartedEvent commandStartedEvent, RequestContext requestContext) {
58+
59+
this.commandStartedEvent = commandStartedEvent;
60+
this.requestContext = requestContext;
61+
this.collectionName = getCollectionName(commandStartedEvent);
62+
}
63+
64+
public CommandStartedEvent getCommandStartedEvent() {
65+
return this.commandStartedEvent;
66+
}
67+
68+
public RequestContext getRequestContext() {
69+
return this.requestContext;
70+
}
71+
72+
public String getCollectionName() {
73+
return this.collectionName;
74+
}
75+
76+
public String getContextualName() {
77+
78+
if (this.collectionName == null) {
79+
return this.commandStartedEvent.getCommandName();
80+
}
81+
82+
return this.commandStartedEvent.getCommandName() + " " + this.collectionName;
83+
}
84+
85+
public void setCommandSucceededEvent(CommandSucceededEvent commandSucceededEvent) {
86+
this.commandSucceededEvent = commandSucceededEvent;
87+
}
88+
89+
public void setCommandFailedEvent(CommandFailedEvent commandFailedEvent) {
90+
this.commandFailedEvent = commandFailedEvent;
91+
}
92+
93+
/**
94+
* Transform the command name into a collection name;
95+
*
96+
* @param event the {@link CommandStartedEvent}
97+
* @return the name of the collection based on the command
98+
*/
99+
@Nullable
100+
private static String getCollectionName(CommandStartedEvent event) {
101+
102+
String commandName = event.getCommandName();
103+
BsonDocument command = event.getCommand();
104+
105+
if (COMMANDS_WITH_COLLECTION_NAME.contains(commandName)) {
106+
107+
String collectionName = getNonEmptyBsonString(command.get(commandName));
108+
109+
if (collectionName != null) {
110+
return collectionName;
111+
}
112+
}
113+
114+
// Some other commands, like getMore, have a field like {"collection": collectionName}.
115+
return getNonEmptyBsonString(command.get("collection"));
116+
}
117+
118+
/**
119+
* Utility method to convert {@link BsonValue} into a plain string.
120+
*
121+
* @return trimmed string from {@code bsonValue} or null if the trimmed string was empty or the value wasn't a string
122+
*/
123+
@Nullable
124+
private static String getNonEmptyBsonString(BsonValue bsonValue) {
125+
126+
if (bsonValue == null || !bsonValue.isString()) {
127+
return null;
128+
}
129+
130+
String stringValue = bsonValue.asString().getValue().trim();
131+
132+
return stringValue.isEmpty() ? null : stringValue;
133+
}
134+
135+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2013-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.mongodb.observability;
17+
18+
import io.micrometer.core.instrument.observation.Observation;
19+
20+
/**
21+
* {@link Observation.TagsProvider} for {@link MongoHandlerContext}.
22+
*
23+
* @author Greg Turnquist
24+
* @since 4.0.0
25+
*/
26+
public interface MongoHandlerTagsProvider extends Observation.TagsProvider<MongoHandlerContext> {
27+
28+
@Override
29+
default boolean supportsContext(Observation.Context context) {
30+
return context instanceof MongoHandlerContext;
31+
}
32+
}

0 commit comments

Comments
 (0)