Skip to content

Commit bf91627

Browse files
committed
Add DefaultMongodbObservationConvention and move tag production out of TracingManager
Replaced all imperative tagLowCardinality/tagHighCardinality calls with a convention-based approach. TracingManager and InternalStreamConnection now populate domain fields on MongodbContext, and DefaultMongodbObservationConvention reads those fields at stop time to produce the final key-values. This decouples tag naming from span creation, enabling users to register a GlobalObservationConvention<MongodbContext> to customize tag names for their environment (e.g. Spring Boot tag alignment with their existing DefaultMongoCommandTagsProvider). Added domain fields to MongodbContext: observationType, commandName, databaseName, collectionName, serverAddress, connectionId, cursorId, transactionNumber, sessionId, queryText, responseStatusCode. Removed tagLowCardinality/tagHighCardinality from the Span interface as they are no longer used.
1 parent 52efa25 commit bf91627

File tree

9 files changed

+406
-185
lines changed

9 files changed

+406
-185
lines changed

config/checkstyle/suppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060

6161
<!-- Allow printStackTrace in this file -->
6262
<suppress checks="Regexp" files="CallbackResultHolder"/>
63-
<suppress checks="Regexp" files="MicrometerTracer"/>
63+
<suppress checks="Regexp" files="DefaultMongodbObservationConvention"/>
6464

6565
<!--Do not check documentation tests classes -->
6666
<suppress checks="Javadoc*" files=".*documentation.*"/>

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import com.mongodb.internal.diagnostics.logging.Logger;
5050
import com.mongodb.internal.diagnostics.logging.Loggers;
5151
import com.mongodb.internal.logging.StructuredLogger;
52+
import com.mongodb.internal.observability.micrometer.MongodbContext;
5253
import com.mongodb.internal.observability.micrometer.Span;
5354
import com.mongodb.internal.session.SessionContext;
5455
import com.mongodb.internal.time.Timeout;
@@ -94,8 +95,7 @@
9495
import static com.mongodb.internal.connection.ProtocolHelper.getSnapshotTimestamp;
9596
import static com.mongodb.internal.connection.ProtocolHelper.isCommandOk;
9697
import static com.mongodb.internal.logging.LogMessage.Level.DEBUG;
97-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.QUERY_TEXT;
98-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.CommandLowCardinalityKeyNames.RESPONSE_STATUS_CODE;
98+
9999
import static com.mongodb.internal.thread.InterruptionUtil.translateInterruptedException;
100100
import static java.util.Arrays.asList;
101101

@@ -473,7 +473,7 @@ private <T> T sendAndReceiveInternal(final CommandMessage message, final Decoder
473473
commandEventSender = new NoOpCommandEventSender();
474474
}
475475
if (isTracingCommandPayloadNeeded) {
476-
tracingSpan.tagHighCardinality(QUERY_TEXT.asString(), commandDocument);
476+
tracingSpan.setQueryText(commandDocument);
477477
}
478478

479479
try {
@@ -585,7 +585,10 @@ private <T> T receiveCommandMessageResponse(final Decoder<T> decoder, final Comm
585585
}
586586
if (tracingSpan != null) {
587587
if (e instanceof MongoCommandException) {
588-
tracingSpan.tagLowCardinality(RESPONSE_STATUS_CODE.withValue(String.valueOf(((MongoCommandException) e).getErrorCode())));
588+
MongodbContext ctx = tracingSpan.getMongodbContext();
589+
if (ctx != null) {
590+
ctx.setResponseStatusCode(String.valueOf(((MongoCommandException) e).getErrorCode()));
591+
}
589592
}
590593
tracingSpan.error(e);
591594
}
@@ -639,16 +642,18 @@ private <T> void sendAndReceiveAsyncInternal(final CommandMessage message, final
639642
commandEventSender = new NoOpCommandEventSender();
640643
}
641644
if (isTracingCommandPayloadNeeded) {
642-
tracingSpan.tagHighCardinality(QUERY_TEXT.asString(), commandDocument);
645+
tracingSpan.setQueryText(commandDocument);
643646
}
644647

645648
final Span commandSpan = tracingSpan;
646649
SingleResultCallback<T> tracingCallback = commandSpan == null ? callback : (result, t) -> {
647650
try {
648651
if (t != null) {
649652
if (t instanceof MongoCommandException) {
650-
commandSpan.tagLowCardinality(
651-
RESPONSE_STATUS_CODE.withValue(String.valueOf(((MongoCommandException) t).getErrorCode())));
653+
MongodbContext ctx = commandSpan.getMongodbContext();
654+
if (ctx != null) {
655+
ctx.setResponseStatusCode(String.valueOf(((MongoCommandException) t).getErrorCode()));
656+
}
652657
}
653658
commandSpan.error(t);
654659
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
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+
* http://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 com.mongodb.internal.observability.micrometer;
18+
19+
import io.micrometer.common.KeyValues;
20+
import io.micrometer.observation.GlobalObservationConvention;
21+
import io.micrometer.observation.Observation;
22+
23+
import java.io.PrintWriter;
24+
import java.io.StringWriter;
25+
26+
/**
27+
* Default {@link ObservationConvention} for MongoDB observations.
28+
* <p>
29+
* Reads domain fields from {@link MongodbContext} and produces the standard MongoDB
30+
* low-cardinality and high-cardinality key-values. Users can override this by registering
31+
* a {@code GlobalObservationConvention<MongodbContext>} on their {@code ObservationRegistry}.
32+
* </p>
33+
*
34+
* @since 5.7
35+
*/
36+
public class DefaultMongodbObservationConvention implements GlobalObservationConvention<MongodbContext> {
37+
38+
@Override
39+
public boolean supportsContext(final Observation.Context context) {
40+
return context instanceof MongodbContext;
41+
}
42+
43+
@Override
44+
public KeyValues getLowCardinalityKeyValues(final MongodbContext context) {
45+
if (context.getObservationType() == MongodbObservation.MONGODB_OPERATION) {
46+
return getOperationLowCardinalityKeyValues(context);
47+
} else {
48+
return getCommandLowCardinalityKeyValues(context);
49+
}
50+
}
51+
52+
@Override
53+
public KeyValues getHighCardinalityKeyValues(final MongodbContext context) {
54+
if (context.getObservationType() == MongodbObservation.MONGODB_COMMAND) {
55+
return getCommandHighCardinalityKeyValues(context);
56+
}
57+
return KeyValues.empty();
58+
}
59+
60+
private KeyValues getOperationLowCardinalityKeyValues(final MongodbContext context) {
61+
String commandName = context.getCommandName();
62+
String databaseName = context.getDatabaseName();
63+
String collectionName = context.getCollectionName();
64+
65+
KeyValues kv = KeyValues.of(
66+
MongodbObservation.OperationLowCardinalityKeyNames.SYSTEM.withValue("mongodb"));
67+
68+
if (databaseName != null) {
69+
kv = kv.and(MongodbObservation.OperationLowCardinalityKeyNames.NAMESPACE.withValue(databaseName));
70+
}
71+
if (collectionName != null) {
72+
kv = kv.and(MongodbObservation.OperationLowCardinalityKeyNames.COLLECTION.withValue(collectionName));
73+
}
74+
if (commandName != null) {
75+
String dbName = databaseName != null ? databaseName : "";
76+
String summary = commandName + " " + dbName
77+
+ (collectionName != null ? "." + collectionName : "");
78+
kv = kv.and(
79+
MongodbObservation.OperationLowCardinalityKeyNames.OPERATION_NAME.withValue(commandName),
80+
MongodbObservation.OperationLowCardinalityKeyNames.OPERATION_SUMMARY.withValue(summary));
81+
}
82+
return kv;
83+
}
84+
85+
private KeyValues getCommandLowCardinalityKeyValues(final MongodbContext context) {
86+
String commandName = context.getCommandName();
87+
String databaseName = context.getDatabaseName();
88+
String collectionName = context.getCollectionName();
89+
String cmdName = commandName != null ? commandName : "";
90+
String dbName = databaseName != null ? databaseName : "";
91+
String summary = cmdName + " " + dbName
92+
+ (collectionName != null ? "." + collectionName : "");
93+
94+
KeyValues kv = KeyValues.of(
95+
MongodbObservation.CommandLowCardinalityKeyNames.SYSTEM.withValue("mongodb"),
96+
MongodbObservation.CommandLowCardinalityKeyNames.NAMESPACE.withValue(dbName),
97+
MongodbObservation.CommandLowCardinalityKeyNames.QUERY_SUMMARY.withValue(summary),
98+
MongodbObservation.CommandLowCardinalityKeyNames.COMMAND_NAME.withValue(cmdName));
99+
if (collectionName != null) {
100+
kv = kv.and(MongodbObservation.CommandLowCardinalityKeyNames.COLLECTION.withValue(collectionName));
101+
}
102+
com.mongodb.ServerAddress serverAddress = context.getServerAddress();
103+
if (serverAddress != null) {
104+
kv = kv.and(
105+
MongodbObservation.CommandLowCardinalityKeyNames.SERVER_ADDRESS.withValue(serverAddress.getHost()),
106+
MongodbObservation.CommandLowCardinalityKeyNames.SERVER_PORT.withValue(
107+
String.valueOf(serverAddress.getPort())),
108+
MongodbObservation.CommandLowCardinalityKeyNames.NETWORK_TRANSPORT.withValue(
109+
context.isUnixSocket() ? "unix" : "tcp"));
110+
}
111+
String responseStatusCode = context.getResponseStatusCode();
112+
if (responseStatusCode != null) {
113+
kv = kv.and(MongodbObservation.CommandLowCardinalityKeyNames.RESPONSE_STATUS_CODE.withValue(responseStatusCode));
114+
}
115+
return kv;
116+
}
117+
118+
private KeyValues getCommandHighCardinalityKeyValues(final MongodbContext context) {
119+
KeyValues kv = KeyValues.empty();
120+
121+
String queryText = context.getQueryText();
122+
if (queryText != null) {
123+
kv = kv.and(MongodbObservation.HighCardinalityKeyNames.QUERY_TEXT.withValue(queryText));
124+
}
125+
com.mongodb.connection.ConnectionId connectionId = context.getConnectionId();
126+
if (connectionId != null) {
127+
kv = kv.and(
128+
MongodbObservation.HighCardinalityKeyNames.CLIENT_CONNECTION_ID.withValue(
129+
String.valueOf(connectionId.getLocalValue())),
130+
MongodbObservation.HighCardinalityKeyNames.SERVER_CONNECTION_ID.withValue(
131+
String.valueOf(connectionId.getServerValue())));
132+
}
133+
Long cursorId = context.getCursorId();
134+
if (cursorId != null) {
135+
kv = kv.and(MongodbObservation.HighCardinalityKeyNames.CURSOR_ID.withValue(
136+
String.valueOf(cursorId)));
137+
}
138+
Long transactionNumber = context.getTransactionNumber();
139+
if (transactionNumber != null) {
140+
kv = kv.and(MongodbObservation.HighCardinalityKeyNames.TRANSACTION_NUMBER.withValue(
141+
String.valueOf(transactionNumber)));
142+
}
143+
String sessionId = context.getSessionId();
144+
if (sessionId != null) {
145+
kv = kv.and(MongodbObservation.HighCardinalityKeyNames.SESSION_ID.withValue(sessionId));
146+
}
147+
148+
// Exception tags from observation error
149+
Throwable error = context.getError();
150+
if (error != null) {
151+
kv = kv.and(
152+
MongodbObservation.HighCardinalityKeyNames.EXCEPTION_MESSAGE.withValue(error.getMessage()),
153+
MongodbObservation.HighCardinalityKeyNames.EXCEPTION_TYPE.withValue(error.getClass().getName()),
154+
MongodbObservation.HighCardinalityKeyNames.EXCEPTION_STACKTRACE.withValue(getStackTraceAsString(error)));
155+
}
156+
157+
return kv;
158+
}
159+
160+
private static String getStackTraceAsString(final Throwable throwable) {
161+
StringWriter sw = new StringWriter();
162+
PrintWriter pw = new PrintWriter(sw);
163+
throwable.printStackTrace(pw);
164+
return sw.toString();
165+
}
166+
}

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

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
import com.mongodb.MongoNamespace;
2020
import com.mongodb.lang.Nullable;
21-
import io.micrometer.common.KeyValue;
22-
import io.micrometer.common.KeyValues;
2321
import io.micrometer.observation.Observation;
2422
import io.micrometer.observation.ObservationRegistry;
2523
import org.bson.BsonDocument;
@@ -28,12 +26,8 @@
2826
import org.bson.json.JsonWriter;
2927
import org.bson.json.JsonWriterSettings;
3028

31-
import java.io.PrintWriter;
3229
import java.io.StringWriter;
3330

34-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.EXCEPTION_MESSAGE;
35-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.EXCEPTION_STACKTRACE;
36-
import static com.mongodb.internal.observability.micrometer.MongodbObservation.HighCardinalityKeyNames.EXCEPTION_TYPE;
3731
import static com.mongodb.internal.observability.micrometer.TracingManager.ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH;
3832
import static java.lang.System.getenv;
3933
import static java.util.Optional.ofNullable;
@@ -66,6 +60,10 @@ public MicrometerTracer(final ObservationRegistry observationRegistry, final boo
6660
this.textMaxLength = ofNullable(getenv(ENV_OBSERVABILITY_QUERY_TEXT_MAX_LENGTH))
6761
.map(Integer::parseInt)
6862
.orElse(textMaxLength);
63+
// Register default convention. Users can override by registering their own GlobalObservationConvention
64+
// after the MongoClient is created — the last matching convention wins.
65+
DefaultMongodbObservationConvention defaultConvention = new DefaultMongodbObservationConvention();
66+
observationRegistry.observationConfig().observationConvention(defaultConvention);
6967
}
7068

7169
@Override
@@ -94,8 +92,11 @@ public boolean includeCommandPayload() {
9492
}
9593

9694
private Observation getObservation(final MongodbObservation observationType, final String name) {
97-
return observationType.observation(observationRegistry, MongodbContext::new)
98-
.contextualName(name);
95+
return observationType.observation(observationRegistry, () -> {
96+
MongodbContext ctx = new MongodbContext();
97+
ctx.setObservationType(observationType);
98+
return ctx;
99+
}).contextualName(name);
99100
}
100101
/**
101102
* Represents a Micrometer-based trace context.
@@ -136,31 +137,13 @@ private static class MicrometerSpan implements Span {
136137
}
137138

138139
@Override
139-
public void tagLowCardinality(final KeyValue keyValue) {
140-
observation.lowCardinalityKeyValue(keyValue);
141-
}
142-
143-
@Override
144-
public void tagLowCardinality(final KeyValues keyValues) {
145-
observation.lowCardinalityKeyValues(keyValues);
146-
}
147-
148-
@Override
149-
public void tagHighCardinality(final KeyValue keyValue) {
150-
observation.highCardinalityKeyValue(keyValue);
151-
}
152-
153-
@Override
154-
public void tagHighCardinality(final KeyValues keyValues) {
155-
observation.highCardinalityKeyValues(keyValues);
156-
}
157-
158-
@Override
159-
public void tagHighCardinality(final String keyName, final BsonDocument value) {
160-
observation.highCardinalityKeyValue(keyName,
161-
(queryTextLength < Integer.MAX_VALUE) // truncate values that are too long
162-
? getTruncatedBsonDocument(value)
163-
: value.toString());
140+
public void setQueryText(final BsonDocument commandDocument) {
141+
MongodbContext ctx = getMongodbContext();
142+
if (ctx != null) {
143+
ctx.setQueryText((queryTextLength < Integer.MAX_VALUE)
144+
? getTruncatedBsonDocument(commandDocument)
145+
: commandDocument.toString());
146+
}
164147
}
165148

166149
@Override
@@ -170,11 +153,6 @@ public void event(final String event) {
170153

171154
@Override
172155
public void error(final Throwable throwable) {
173-
observation.highCardinalityKeyValues(KeyValues.of(
174-
EXCEPTION_MESSAGE.withValue(throwable.getMessage()),
175-
EXCEPTION_TYPE.withValue(throwable.getClass().getName()),
176-
EXCEPTION_STACKTRACE.withValue(getStackTraceAsString(throwable))
177-
));
178156
observation.error(throwable);
179157
}
180158

@@ -190,15 +168,17 @@ public TraceContext context() {
190168

191169
@Override
192170
@Nullable
193-
public MongoNamespace getNamespace() {
194-
return namespace;
171+
public MongodbContext getMongodbContext() {
172+
if (observation.getContext() instanceof MongodbContext) {
173+
return (MongodbContext) observation.getContext();
174+
}
175+
return null;
195176
}
196177

197-
private String getStackTraceAsString(final Throwable throwable) {
198-
StringWriter sw = new StringWriter();
199-
PrintWriter pw = new PrintWriter(sw);
200-
throwable.printStackTrace(pw);
201-
return sw.toString();
178+
@Override
179+
@Nullable
180+
public MongoNamespace getNamespace() {
181+
return namespace;
202182
}
203183

204184
private String getTruncatedBsonDocument(final BsonDocument commandDocument) {

0 commit comments

Comments
 (0)