Skip to content

Commit 4912105

Browse files
committed
Split Micrometer observation into separate operation and command types
The driver used a single observation name ("mongodb") for both operation-level and command-level spans, which have different sets of low-cardinality tag keys. Prometheus requires all meters sharing a name to have identical tag key sets, causing the second observation type to be silently dropped. Split MongodbObservation.MONGODB_OBSERVATION into MONGODB_OPERATION (name "mongodb.operation") and MONGODB_COMMAND (name "mongodb.command"), each declaring its own low-cardinality key set. Updated Tracer and TracingManager to pass the observation type through span creation.
1 parent 6c4ab1b commit 4912105

6 files changed

Lines changed: 128 additions & 92 deletions

File tree

driver-core/src/main/com/mongodb/internal/connection/InternalStreamConnection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
import static com.mongodb.internal.connection.ProtocolHelper.isCommandOk;
9696
import static com.mongodb.internal.logging.LogMessage.Level.DEBUG;
9797
import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.QUERY_TEXT;
98-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.RESPONSE_STATUS_CODE;
98+
import static com.mongodb.internal.observability.micrometer.MongodbObservation.CommandLowCardinalityKeyNames.RESPONSE_STATUS_CODE;
9999
import static com.mongodb.internal.thread.InterruptionUtil.translateInterruptedException;
100100
import static java.util.Arrays.asList;
101101

driver-core/src/main/com/mongodb/internal/observability/micrometer/MicrometerTracer.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@
3333
import java.io.PrintWriter;
3434
import java.io.StringWriter;
3535

36-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_MESSAGE;
37-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_STACKTRACE;
38-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.EXCEPTION_TYPE;
39-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.MONGODB_OBSERVATION;
36+
import static com.mongodb.internal.observability.micrometer.MongodbObservation.CommandLowCardinalityKeyNames.EXCEPTION_MESSAGE;
37+
import static com.mongodb.internal.observability.micrometer.MongodbObservation.CommandLowCardinalityKeyNames.EXCEPTION_STACKTRACE;
38+
import static com.mongodb.internal.observability.micrometer.MongodbObservation.CommandLowCardinalityKeyNames.EXCEPTION_TYPE;
4039
import static com.mongodb.internal.observability.micrometer.TracingManager.ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH;
4140
import static java.lang.System.getenv;
4241
import static java.util.Optional.ofNullable;
@@ -81,8 +80,9 @@ public MicrometerTracer(final ObservationRegistry observationRegistry, final boo
8180
}
8281

8382
@Override
84-
public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) {
85-
Observation observation = getObservation(name);
83+
public Span nextSpan(final MongodbObservation observationType, final String name,
84+
@Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) {
85+
Observation observation = getObservation(observationType, name);
8686

8787
if (parent instanceof MicrometerTraceContext) {
8888
Observation parentObservation = ((MicrometerTraceContext) parent).observation;
@@ -104,8 +104,8 @@ public boolean includeCommandPayload() {
104104
return allowCommandPayload;
105105
}
106106

107-
private Observation getObservation(final String name) {
108-
Observation observation = MONGODB_OBSERVATION.observation(observationRegistry,
107+
private Observation getObservation(final MongodbObservation observationType, final String name) {
108+
Observation observation = observationType.observation(observationRegistry,
109109
() -> new SenderContext<>((carrier, key, value) -> {}, Kind.CLIENT))
110110
.contextualName(name);
111111
observation.getContext().put(QUERY_TEXT_LENGTH_CONTEXT_KEY, textMaxLength);

driver-core/src/main/com/mongodb/internal/observability/micrometer/MongodbObservation.java

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,58 @@
2121
import io.micrometer.observation.docs.ObservationDocumentation;
2222

2323
/**
24-
* A MongoDB-based {@link Observation}.
24+
* MongoDB {@link ObservationDocumentation} definitions for operation-level and command-level observations.
25+
* <p>
26+
* These are split into two separate observation types so that each has a distinct name and a fixed set
27+
* of low-cardinality tag keys. This is required by Prometheus which rejects meters that share a name
28+
* but have different tag key sets.
29+
* </p>
2530
*
2631
* @since 5.7
2732
*/
2833
public enum MongodbObservation implements ObservationDocumentation {
2934

30-
MONGODB_OBSERVATION {
35+
/**
36+
* Observation for high-level MongoDB operations (e.g. find, insert, update).
37+
* Created per user-initiated operation, may contain multiple command spans.
38+
*/
39+
MONGODB_OPERATION {
3140
@Override
3241
public String getName() {
33-
return "mongodb";
42+
return "mongodb.operation";
3443
}
3544

3645
@Override
3746
public KeyName[] getLowCardinalityKeyNames() {
38-
return LowCardinalityKeyNames.values();
47+
return OperationLowCardinalityKeyNames.values();
48+
}
49+
},
50+
51+
/**
52+
* Observation for wire-protocol MongoDB commands sent to the server.
53+
* Created per actual command (nested under an operation span).
54+
*/
55+
MONGODB_COMMAND {
56+
@Override
57+
public String getName() {
58+
return "mongodb.command";
59+
}
60+
61+
@Override
62+
public KeyName[] getLowCardinalityKeyNames() {
63+
return CommandLowCardinalityKeyNames.values();
3964
}
4065

4166
@Override
4267
public KeyName[] getHighCardinalityKeyNames() {
4368
return HighCardinalityKeyNames.values();
4469
}
45-
4670
};
4771

4872
/**
49-
* Enums related to low cardinality key names for MongoDB tags.
73+
* Low cardinality key names for operation-level observations.
5074
*/
51-
public enum LowCardinalityKeyNames implements KeyName {
75+
public enum OperationLowCardinalityKeyNames implements KeyName {
5276

5377
SYSTEM {
5478
@Override
@@ -74,22 +98,41 @@ public String asString() {
7498
return "db.operation.name";
7599
}
76100
},
77-
COMMAND_NAME {
101+
OPERATION_SUMMARY {
78102
@Override
79103
public String asString() {
80-
return "db.command.name";
104+
return "db.operation.summary";
105+
}
106+
}
107+
}
108+
109+
/**
110+
* Low cardinality key names for command-level observations.
111+
*/
112+
public enum CommandLowCardinalityKeyNames implements KeyName {
113+
114+
SYSTEM {
115+
@Override
116+
public String asString() {
117+
return "db.system";
81118
}
82119
},
83-
NETWORK_TRANSPORT {
120+
NAMESPACE {
84121
@Override
85122
public String asString() {
86-
return "network.transport";
123+
return "db.namespace";
87124
}
88125
},
89-
OPERATION_SUMMARY {
126+
COLLECTION {
90127
@Override
91128
public String asString() {
92-
return "db.operation.summary";
129+
return "db.collection.name";
130+
}
131+
},
132+
COMMAND_NAME {
133+
@Override
134+
public String asString() {
135+
return "db.command.name";
93136
}
94137
},
95138
QUERY_SUMMARY {
@@ -98,10 +141,10 @@ public String asString() {
98141
return "db.query.summary";
99142
}
100143
},
101-
CURSOR_ID {
144+
NETWORK_TRANSPORT {
102145
@Override
103146
public String asString() {
104-
return "db.mongodb.cursor_id";
147+
return "network.transport";
105148
}
106149
},
107150
SERVER_ADDRESS {
@@ -128,6 +171,12 @@ public String asString() {
128171
return "db.mongodb.server_connection_id";
129172
}
130173
},
174+
CURSOR_ID {
175+
@Override
176+
public String asString() {
177+
return "db.mongodb.cursor_id";
178+
}
179+
},
131180
TRANSACTION_NUMBER {
132181
@Override
133182
public String asString() {
@@ -167,7 +216,7 @@ public String asString() {
167216
}
168217

169218
/**
170-
* Enums related to high cardinality (highly variable values) key names for MongoDB tags.
219+
* High cardinality (highly variable values) key names for command-level observations.
171220
*/
172221
public enum HighCardinalityKeyNames implements KeyName {
173222

driver-core/src/main/com/mongodb/internal/observability/micrometer/Tracer.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
public interface Tracer {
3131
Tracer NO_OP = new Tracer() {
3232
@Override
33-
public Span nextSpan(final String name, @Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) {
33+
public Span nextSpan(final MongodbObservation observationType, final String name,
34+
@Nullable final TraceContext parent, @Nullable final MongoNamespace namespace) {
3435
return Span.EMPTY;
3536
}
3637

@@ -46,14 +47,15 @@ public boolean includeCommandPayload() {
4647
};
4748

4849
/**
49-
* Creates a new span with the specified name and optional parent trace context.
50+
* Creates a new span with the specified observation type, name and optional parent trace context.
5051
*
52+
* @param observationType The {@link MongodbObservation} type (operation or command).
5153
* @param name The name of the span.
5254
* @param parent The parent {@link TraceContext}, or null if no parent context is provided.
5355
* @param namespace The {@link MongoNamespace} associated with the span, or null if none is provided.
5456
* @return A {@link Span} representing the newly created span.
5557
*/
56-
Span nextSpan(String name, @Nullable TraceContext parent, @Nullable MongoNamespace namespace);
58+
Span nextSpan(MongodbObservation observationType, String name, @Nullable TraceContext parent, @Nullable MongoNamespace namespace);
5759

5860
/**
5961
* Indicates whether tracing is enabled.

driver-core/src/main/com/mongodb/internal/observability/micrometer/TracingManager.java

Lines changed: 43 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,10 @@
3434
import java.util.function.Predicate;
3535
import java.util.function.Supplier;
3636

37-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.CLIENT_CONNECTION_ID;
38-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.COLLECTION;
39-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.COMMAND_NAME;
40-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.NAMESPACE;
41-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.OPERATION_NAME;
42-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.OPERATION_SUMMARY;
43-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.NETWORK_TRANSPORT;
44-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.QUERY_SUMMARY;
45-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SERVER_ADDRESS;
46-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SERVER_CONNECTION_ID;
47-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SERVER_PORT;
48-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SESSION_ID;
49-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.SYSTEM;
50-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.TRANSACTION_NUMBER;
51-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.LowCardinalityKeyNames.CURSOR_ID;
37+
import static com.mongodb.internal.observability.micrometer.MongodbObservation.MONGODB_COMMAND;
38+
import static com.mongodb.internal.observability.micrometer.MongodbObservation.MONGODB_OPERATION;
39+
import static com.mongodb.internal.observability.micrometer.MongodbObservation.CommandLowCardinalityKeyNames;
40+
import static com.mongodb.internal.observability.micrometer.MongodbObservation.OperationLowCardinalityKeyNames;
5241
import static java.lang.System.getenv;
5342

5443
/**
@@ -109,35 +98,31 @@ public TracingManager(@Nullable final ObservabilitySettings observabilitySetting
10998
}
11099

111100
/**
112-
* Creates a new span with the specified name and parent trace context.
113-
* <p>
114-
* This method is used to create a span that is linked to a parent context,
115-
* enabling hierarchical tracing of operations.
116-
* </p>
101+
* Creates a new span with the specified observation type, name and parent trace context.
117102
*
118-
* @param name The name of the span.
119-
* @param parentContext The parent trace context to associate with the span.
103+
* @param observationType The observation type (operation or command).
104+
* @param name The name of the span.
105+
* @param parentContext The parent trace context to associate with the span.
120106
* @return The created span.
121107
*/
122-
public Span addSpan(final String name, @Nullable final TraceContext parentContext) {
123-
return tracer.nextSpan(name, parentContext, null);
108+
public Span addSpan(final MongodbObservation observationType, final String name,
109+
@Nullable final TraceContext parentContext) {
110+
return tracer.nextSpan(observationType, name, parentContext, null);
124111
}
125112

126113
/**
127-
* Creates a new span with the specified name, parent trace context, and MongoDB namespace.
128-
* <p>
129-
* This method is used to create a span that is linked to a parent context,
130-
* enabling hierarchical tracing of operations. The MongoDB namespace can be used
131-
* by nested spans to access the database and collection name (which might not be easily accessible at connection layer).
132-
* </p>
114+
* Creates a new span with the specified observation type, name, parent trace context,
115+
* and MongoDB namespace.
133116
*
134-
* @param name The name of the span.
135-
* @param parentContext The parent trace context to associate with the span.
136-
* @param namespace The MongoDB namespace associated with the operation.
117+
* @param observationType The observation type (operation or command).
118+
* @param name The name of the span.
119+
* @param parentContext The parent trace context to associate with the span.
120+
* @param namespace The MongoDB namespace associated with the operation.
137121
* @return The created span.
138122
*/
139-
public Span addSpan(final String name, @Nullable final TraceContext parentContext, final MongoNamespace namespace) {
140-
return tracer.nextSpan(name, parentContext, namespace);
123+
public Span addSpan(final MongodbObservation observationType, final String name,
124+
@Nullable final TraceContext parentContext, final MongoNamespace namespace) {
125+
return tracer.nextSpan(observationType, name, parentContext, namespace);
141126
}
142127

143128
/**
@@ -146,8 +131,8 @@ public Span addSpan(final String name, @Nullable final TraceContext parentContex
146131
* @return The created transaction span.
147132
*/
148133
public Span addTransactionSpan() {
149-
Span span = tracer.nextSpan("transaction", null, null);
150-
span.tagLowCardinality(SYSTEM.withValue("mongodb"));
134+
Span span = tracer.nextSpan(MONGODB_OPERATION, "transaction", null, null);
135+
span.tagLowCardinality(OperationLowCardinalityKeyNames.SYSTEM.withValue("mongodb"));
151136
return span;
152137
}
153138

@@ -205,13 +190,13 @@ public Span createTracingSpan(final CommandMessage message,
205190
}
206191

207192
Span operationSpan = operationContext.getTracingSpan();
208-
Span span = addSpan(commandName, operationSpan != null ? operationSpan.context() : null);
193+
Span span = addSpan(MONGODB_COMMAND, commandName, operationSpan != null ? operationSpan.context() : null);
209194

210195
if (command.containsKey("getMore")) {
211196
long cursorId = command.getInt64("getMore").longValue();
212-
span.tagLowCardinality(CURSOR_ID.withValue(String.valueOf(cursorId)));
197+
span.tagLowCardinality(CommandLowCardinalityKeyNames.CURSOR_ID.withValue(String.valueOf(cursorId)));
213198
if (operationSpan != null) {
214-
operationSpan.tagLowCardinality(CURSOR_ID.withValue(String.valueOf(cursorId)));
199+
operationSpan.tagLowCardinality(CommandLowCardinalityKeyNames.CURSOR_ID.withValue(String.valueOf(cursorId)));
215200
}
216201
}
217202

@@ -234,33 +219,33 @@ public Span createTracingSpan(final CommandMessage message,
234219
String summary = commandName + " " + namespace + (collection.isEmpty() ? "" : "." + collection);
235220

236221
KeyValues keyValues = KeyValues.of(
237-
SYSTEM.withValue("mongodb"),
238-
NAMESPACE.withValue(namespace),
239-
QUERY_SUMMARY.withValue(summary),
240-
COMMAND_NAME.withValue(commandName));
222+
CommandLowCardinalityKeyNames.SYSTEM.withValue("mongodb"),
223+
CommandLowCardinalityKeyNames.NAMESPACE.withValue(namespace),
224+
CommandLowCardinalityKeyNames.QUERY_SUMMARY.withValue(summary),
225+
CommandLowCardinalityKeyNames.COMMAND_NAME.withValue(commandName));
241226

242227
if (!collection.isEmpty()) {
243-
keyValues = keyValues.and(COLLECTION.withValue(collection));
228+
keyValues = keyValues.and(CommandLowCardinalityKeyNames.COLLECTION.withValue(collection));
244229
}
245230
span.tagLowCardinality(keyValues);
246231

247232
// tag server and connection info
248233
ServerAddress serverAddress = serverAddressSupplier.get();
249234
ConnectionId connectionId = connectionIdSupplier.get();
250235
span.tagLowCardinality(KeyValues.of(
251-
SERVER_ADDRESS.withValue(serverAddress.getHost()),
252-
SERVER_PORT.withValue(String.valueOf(serverAddress.getPort())),
253-
CLIENT_CONNECTION_ID.withValue(String.valueOf(connectionId.getLocalValue())),
254-
SERVER_CONNECTION_ID.withValue(String.valueOf(connectionId.getServerValue())),
255-
NETWORK_TRANSPORT.withValue(serverAddress instanceof UnixServerAddress ? "unix" : "tcp")
236+
CommandLowCardinalityKeyNames.SERVER_ADDRESS.withValue(serverAddress.getHost()),
237+
CommandLowCardinalityKeyNames.SERVER_PORT.withValue(String.valueOf(serverAddress.getPort())),
238+
CommandLowCardinalityKeyNames.CLIENT_CONNECTION_ID.withValue(String.valueOf(connectionId.getLocalValue())),
239+
CommandLowCardinalityKeyNames.SERVER_CONNECTION_ID.withValue(String.valueOf(connectionId.getServerValue())),
240+
CommandLowCardinalityKeyNames.NETWORK_TRANSPORT.withValue(serverAddress instanceof UnixServerAddress ? "unix" : "tcp")
256241
));
257242

258243
// tag session and transaction info
259244
SessionContext sessionContext = operationContext.getSessionContext();
260245
if (sessionContext.hasSession() && !sessionContext.isImplicitSession()) {
261246
span.tagLowCardinality(KeyValues.of(
262-
TRANSACTION_NUMBER.withValue(String.valueOf(sessionContext.getTransactionNumber())),
263-
SESSION_ID.withValue(String.valueOf(sessionContext.getSessionId()
247+
CommandLowCardinalityKeyNames.TRANSACTION_NUMBER.withValue(String.valueOf(sessionContext.getTransactionNumber())),
248+
CommandLowCardinalityKeyNames.SESSION_ID.withValue(String.valueOf(sessionContext.getSessionId()
264249
.get(sessionContext.getSessionId().getFirstKey())
265250
.asBinary().asUuid()))
266251
));
@@ -298,15 +283,15 @@ public Span createOperationSpan(@Nullable final TransactionSpan transactionSpan,
298283
: "." + namespace.getCollectionName());
299284

300285
KeyValues keyValues = KeyValues.of(
301-
SYSTEM.withValue("mongodb"),
302-
NAMESPACE.withValue(namespace.getDatabaseName()));
286+
OperationLowCardinalityKeyNames.SYSTEM.withValue("mongodb"),
287+
OperationLowCardinalityKeyNames.NAMESPACE.withValue(namespace.getDatabaseName()));
303288
if (!MongoNamespaceHelper.COMMAND_COLLECTION_NAME.equalsIgnoreCase(namespace.getCollectionName())) {
304-
keyValues = keyValues.and(COLLECTION.withValue(namespace.getCollectionName()));
289+
keyValues = keyValues.and(OperationLowCardinalityKeyNames.COLLECTION.withValue(namespace.getCollectionName()));
305290
}
306-
keyValues = keyValues.and(OPERATION_NAME.withValue(commandName),
307-
OPERATION_SUMMARY.withValue(name));
291+
keyValues = keyValues.and(OperationLowCardinalityKeyNames.OPERATION_NAME.withValue(commandName),
292+
OperationLowCardinalityKeyNames.OPERATION_SUMMARY.withValue(name));
308293

309-
Span span = addSpan(name, parentContext, namespace);
294+
Span span = addSpan(MONGODB_OPERATION, name, parentContext, namespace);
310295
span.tagLowCardinality(keyValues);
311296
operationContext.setTracingSpan(span);
312297
return span;

0 commit comments

Comments
 (0)