Skip to content

Commit 012a2f7

Browse files
WIP
1 parent a525237 commit 012a2f7

File tree

6 files changed

+451
-4
lines changed

6 files changed

+451
-4
lines changed

pom.xml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>org.springframework.data</groupId>
77
<artifactId>spring-data-mongodb-parent</artifactId>
8-
<version>4.0.0-SNAPSHOT</version>
8+
<version>3.4.0-SNAPSHOT</version>
99
<packaging>pom</packaging>
1010

1111
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
1515
<parent>
1616
<groupId>org.springframework.data.build</groupId>
1717
<artifactId>spring-data-parent</artifactId>
18-
<version>3.0.0-SNAPSHOT</version>
18+
<version>2.7.0-GH-1617-SNAPSHOT</version>
1919
</parent>
2020

2121
<modules>
@@ -24,10 +24,9 @@
2424
</modules>
2525

2626
<properties>
27-
<source.level>16</source.level>
2827
<project.type>multi</project.type>
2928
<dist.id>spring-data-mongodb</dist.id>
30-
<springdata.commons>3.0.0-SNAPSHOT</springdata.commons>
29+
<springdata.commons>2.7.0-SNAPSHOT</springdata.commons>
3130
<mongo>4.4.1</mongo>
3231
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
3332
<jmh.version>1.19</jmh.version>

spring-data-mongodb/pom.xml

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

196+
<dependency>
197+
<groupId>io.micrometer</groupId>
198+
<artifactId>micrometer-core</artifactId>
199+
<optional>true</optional>
200+
</dependency>
201+
<dependency>
202+
<groupId>io.micrometer</groupId>
203+
<artifactId>micrometer-tracing</artifactId>
204+
<optional>true</optional>
205+
</dependency>
206+
196207
<dependency>
197208
<groupId>org.hibernate</groupId>
198209
<artifactId>hibernate-validator</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* Copyright 2013-2021 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+
17+
package org.springframework.data.mongodb.observability;
18+
19+
import java.util.Arrays;
20+
import java.util.LinkedHashSet;
21+
import java.util.Set;
22+
23+
import com.mongodb.RequestContext;
24+
import com.mongodb.connection.ConnectionDescription;
25+
import com.mongodb.connection.ConnectionId;
26+
import com.mongodb.event.CommandFailedEvent;
27+
import com.mongodb.event.CommandListener;
28+
import com.mongodb.event.CommandStartedEvent;
29+
import com.mongodb.event.CommandSucceededEvent;
30+
import io.micrometer.core.instrument.MeterRegistry;
31+
import io.micrometer.core.instrument.Tag;
32+
import io.micrometer.core.instrument.Tags;
33+
import io.micrometer.core.instrument.Timer;
34+
import org.apache.commons.logging.Log;
35+
import org.apache.commons.logging.LogFactory;
36+
import org.bson.BsonDocument;
37+
import org.bson.BsonValue;
38+
39+
import org.springframework.lang.Nullable;
40+
41+
/**
42+
* Altered the Brave MongoDb instrumentation code. The code is available here:
43+
* https://github.com/openzipkin/brave/blob/release-5.13.0/instrumentation/mongodb/src/main/java/brave/mongodb/TraceMongoCommandListener.java
44+
*
45+
* @author OpenZipkin Brave Authors
46+
* @author Marcin Grzejszczak
47+
* @since 3.0.0
48+
*/
49+
public final class MicrometerMongoCommandListener implements CommandListener {
50+
51+
private static final Log log = LogFactory.getLog(MicrometerMongoCommandListener.class);
52+
53+
// See https://docs.mongodb.com/manual/reference/command for the command reference
54+
static final Set<String> COMMANDS_WITH_COLLECTION_NAME = new LinkedHashSet<>(
55+
Arrays.asList("aggregate", "count", "distinct", "mapReduce", "geoSearch", "delete", "find", "findAndModify",
56+
"insert", "update", "collMod", "compact", "convertToCapped", "create", "createIndexes", "drop",
57+
"dropIndexes", "killCursors", "listIndexes", "reIndex"));
58+
59+
private final MeterRegistry registry;
60+
61+
MicrometerMongoCommandListener(MeterRegistry registry) {
62+
this.registry = registry;
63+
}
64+
65+
@Override
66+
public void commandStarted(CommandStartedEvent event) {
67+
if (log.isDebugEnabled()) {
68+
log.debug("Instrumenting the command started event");
69+
}
70+
String databaseName = event.getDatabaseName();
71+
if ("admin".equals(databaseName)) {
72+
return; // don't trace commands like "endSessions"
73+
}
74+
RequestContext requestContext = event.getRequestContext();
75+
if (requestContext == null) {
76+
return;
77+
}
78+
Timer.Sample parent = sampleFromContext(requestContext);
79+
if (log.isDebugEnabled()) {
80+
log.debug("Found the following sample passed from the mongo context [" + parent + "]");
81+
}
82+
if (parent == null) {
83+
return;
84+
}
85+
setupObservability(event, requestContext);
86+
}
87+
88+
private void setupObservability(CommandStartedEvent event, RequestContext requestContext) {
89+
String commandName = event.getCommandName();
90+
BsonDocument command = event.getCommand();
91+
String collectionName = getCollectionName(command, commandName);
92+
String metricName = getMetricName(commandName, collectionName);
93+
Timer.Builder timerBuilder = Timer.builder(metricName);
94+
MongoHandlerContext mongoHandlerContext = new MongoHandlerContext(event) {
95+
@Override public Tags getLowCardinalityTags() {
96+
Tags tags = Tags.empty();
97+
if (collectionName != null) {
98+
tags = tags.and(Tag.of("mongodb.collection", collectionName));
99+
}
100+
Tag tag = connectionTag(event);
101+
if (tag == null) {
102+
return tags;
103+
}
104+
return tags.and(tag);
105+
}
106+
107+
@Override public Tags getHighCardinalityTags() {
108+
return Tags.of(Tag.of("mongodb.command", commandName));
109+
}
110+
};
111+
Timer.Sample child = Timer.start(this.registry, mongoHandlerContext);
112+
requestContext.put(mongoHandlerContext, MongoHandlerContext.class);
113+
requestContext.put(Timer.Builder.class, timerBuilder);
114+
if (log.isDebugEnabled()) {
115+
log.debug("Created a child sample [" + child
116+
+ "] for mongo instrumentation and put it in mongo context");
117+
}
118+
}
119+
120+
private Tag connectionTag(CommandStartedEvent event) {
121+
ConnectionDescription connectionDescription = event.getConnectionDescription();
122+
if (connectionDescription != null) {
123+
ConnectionId connectionId = connectionDescription.getConnectionId();
124+
if (connectionId != null) {
125+
return Tag.of("mongodb.cluster_id", connectionId.getServerId().getClusterId().getValue());
126+
}
127+
}
128+
return null;
129+
}
130+
131+
private static Timer.Sample sampleFromContext(RequestContext context) {
132+
Timer.Sample sample = context.getOrDefault(Timer.Sample.class, null);
133+
if (sample != null) {
134+
if (log.isDebugEnabled()) {
135+
log.debug("Found a sample in mongo context [" + sample + "]");
136+
}
137+
return sample;
138+
}
139+
if (log.isDebugEnabled()) {
140+
log.debug("No sample was found - will not create any child spans");
141+
}
142+
return null;
143+
}
144+
145+
@Override
146+
public void commandSucceeded(CommandSucceededEvent event) {
147+
RequestContext requestContext = event.getRequestContext();
148+
if (requestContext == null) {
149+
return;
150+
}
151+
Timer.Sample sample = requestContext.getOrDefault(Timer.Sample.class, null);
152+
if (sample == null) {
153+
return;
154+
}
155+
MongoHandlerContext context = requestContext.get(MongoHandlerContext.class);
156+
context.setCommandSucceededEvent(event);
157+
if (log.isDebugEnabled()) {
158+
log.debug("Command succeeded - will stop sample [" + sample + "]");
159+
}
160+
Timer.Builder builder = requestContext.get(Timer.Builder.class);
161+
sample.stop(builder);
162+
requestContext.delete(Timer.Sample.class);
163+
requestContext.delete(MongoHandlerContext.class);
164+
}
165+
166+
@Override
167+
public void commandFailed(CommandFailedEvent event) {
168+
RequestContext requestContext = event.getRequestContext();
169+
if (requestContext == null) {
170+
return;
171+
}
172+
Timer.Sample sample = requestContext.getOrDefault(Timer.Sample.class, null);
173+
if (sample == null) {
174+
return;
175+
}
176+
MongoHandlerContext context = requestContext.get(MongoHandlerContext.class);
177+
context.setCommandFailedEvent(event);
178+
if (log.isDebugEnabled()) {
179+
log.debug("Command failed - will stop sample [" + sample + "]");
180+
}
181+
sample.error(event.getThrowable());
182+
Timer.Builder builder = requestContext.get(Timer.Builder.class);
183+
sample.stop(builder);
184+
requestContext.delete(Timer.Sample.class);
185+
requestContext.delete(MongoHandlerContext.class);
186+
}
187+
188+
@Nullable
189+
String getCollectionName(BsonDocument command, String commandName) {
190+
if (COMMANDS_WITH_COLLECTION_NAME.contains(commandName)) {
191+
String collectionName = getNonEmptyBsonString(command.get(commandName));
192+
if (collectionName != null) {
193+
return collectionName;
194+
}
195+
}
196+
// Some other commands, like getMore, have a field like {"collection":
197+
// collectionName}.
198+
return getNonEmptyBsonString(command.get("collection"));
199+
}
200+
201+
/**
202+
* @return trimmed string from {@code bsonValue} or null if the trimmed string was
203+
* empty or the value wasn't a string
204+
*/
205+
@Nullable
206+
static String getNonEmptyBsonString(BsonValue bsonValue) {
207+
if (bsonValue == null || !bsonValue.isString()) {
208+
return null;
209+
}
210+
String stringValue = bsonValue.asString().getValue().trim();
211+
return stringValue.isEmpty() ? null : stringValue;
212+
}
213+
214+
static String getMetricName(String commandName, @Nullable String collectionName) {
215+
if (collectionName == null) {
216+
return commandName;
217+
}
218+
return commandName + " " + collectionName;
219+
}
220+
221+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2013-2021 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+
17+
package org.springframework.data.mongodb.observability;
18+
19+
import java.util.Map;
20+
import java.util.stream.Stream;
21+
22+
import com.mongodb.RequestContext;
23+
24+
class MicrometerRequestContext implements RequestContext {
25+
26+
private final Map<Object, Object> map;
27+
28+
MicrometerRequestContext(Map<Object, Object> map) {
29+
this.map = map;
30+
}
31+
32+
@Override
33+
public <T> T get(Object key) {
34+
return (T) map.get(key);
35+
}
36+
37+
@Override
38+
public boolean hasKey(Object key) {
39+
return map.containsKey(key);
40+
}
41+
42+
@Override
43+
public boolean isEmpty() {
44+
return map.isEmpty();
45+
}
46+
47+
@Override
48+
public void put(Object key, Object value) {
49+
map.put(key, value);
50+
}
51+
52+
@Override
53+
public void delete(Object key) {
54+
map.remove(key);
55+
}
56+
57+
@Override
58+
public int size() {
59+
return map.size();
60+
}
61+
62+
@Override
63+
public Stream<Map.Entry<Object, Object>> stream() {
64+
return map.entrySet().stream();
65+
}
66+
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2013-2021 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+
17+
package org.springframework.data.mongodb.observability;
18+
19+
import com.mongodb.event.CommandFailedEvent;
20+
import com.mongodb.event.CommandStartedEvent;
21+
import com.mongodb.event.CommandSucceededEvent;
22+
import io.micrometer.core.instrument.Timer;
23+
24+
public class MongoHandlerContext extends Timer.HandlerContext {
25+
26+
private final CommandStartedEvent commandStartedEvent;
27+
28+
private CommandSucceededEvent commandSucceededEvent;
29+
30+
private CommandFailedEvent commandFailedEvent;
31+
32+
public MongoHandlerContext(CommandStartedEvent commandStartedEvent) {
33+
this.commandStartedEvent = commandStartedEvent;
34+
}
35+
36+
public CommandStartedEvent getCommandStartedEvent() {
37+
return commandStartedEvent;
38+
}
39+
40+
public CommandSucceededEvent getCommandSucceededEvent() {
41+
return commandSucceededEvent;
42+
}
43+
44+
public void setCommandSucceededEvent(CommandSucceededEvent commandSucceededEvent) {
45+
this.commandSucceededEvent = commandSucceededEvent;
46+
}
47+
48+
public CommandFailedEvent getCommandFailedEvent() {
49+
return commandFailedEvent;
50+
}
51+
52+
public void setCommandFailedEvent(CommandFailedEvent commandFailedEvent) {
53+
this.commandFailedEvent = commandFailedEvent;
54+
}
55+
}

0 commit comments

Comments
 (0)