diff --git a/.github/native-tests.json b/.github/native-tests.json
index fbd8e62dbe16a..f44c910369ebd 100644
--- a/.github/native-tests.json
+++ b/.github/native-tests.json
@@ -117,7 +117,7 @@
{
"category": "Misc4",
"timeout": 130,
- "test-modules": "picocli-native, gradle, micrometer-mp-metrics, micrometer-prometheus, logging-json, jaxp, jaxb, opentelemetry, opentelemetry-jdbc-instrumentation, opentelemetry-redis-instrumentation, web-dependency-locator",
+ "test-modules": "picocli-native, gradle, micrometer-mp-metrics, micrometer-prometheus, logging-json, jaxp, jaxb, opentelemetry, opentelemetry-jdbc-instrumentation, opentelemetry-mongodb-client-instrumentation, opentelemetry-redis-instrumentation, web-dependency-locator",
"os-name": "ubuntu-latest"
},
{
diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc
index 6093e86a9af16..2e0b50bfa0128 100644
--- a/docs/src/main/asciidoc/mongodb.adoc
+++ b/docs/src/main/asciidoc/mongodb.adoc
@@ -616,6 +616,18 @@ This behavior must first be enabled by setting the `quarkus.mongodb.metrics.enab
So when you access the `/q/metrics` endpoint of your application you will have information about the connection pool status.
When using xref:smallrye-metrics.adoc[SmallRye Metrics], connection pool metrics will be available under the `vendor` scope.
+
+== Tracing
+
+To use tracing with MongoDB, you need to add the xref:opentelemetry.adoc[`quarkus-opentelemetry`] extension to your project.
+
+Even with all the tracing infrastructure in place the mongodb tracing is not enabled by default, and you need to enable it by setting this property:
+[source, properties]
+----
+# enable tracing
+quarkus.mongodb.tracing.enabled=true
+----
+
== Testing helpers
xref:#dev-services[Dev Services for MongoDB] is your best option to start a MongoDB database for your unit tests.
diff --git a/docs/src/main/asciidoc/opentelemetry.adoc b/docs/src/main/asciidoc/opentelemetry.adoc
index 6f08bda91c7ab..974ec37b39b07 100644
--- a/docs/src/main/asciidoc/opentelemetry.adoc
+++ b/docs/src/main/asciidoc/opentelemetry.adoc
@@ -684,6 +684,7 @@ Additional exporters will be available in the Quarkiverse https://docs.quarkiver
* https://quarkus.io/guides/resteasy-client[`quarkus-resteasy-client`]
* https://quarkus.io/guides/scheduler[`quarkus-scheduler`]
* https://quarkus.io/guides/smallrye-graphql[`quarkus-smallrye-graphql`]
+* https://quarkus.io/extensions/io.quarkus/quarkus-mongodb-client[`quarkus-mongodb-client`]
* https://quarkus.io/extensions/io.quarkus/quarkus-messaging[`quarkus-messaging`]
** AMQP 1.0
** RabbitMQ
diff --git a/extensions/mongodb-client/deployment/pom.xml b/extensions/mongodb-client/deployment/pom.xml
index 0c50568794612..ad80149825900 100644
--- a/extensions/mongodb-client/deployment/pom.xml
+++ b/extensions/mongodb-client/deployment/pom.xml
@@ -34,6 +34,11 @@
io.quarkus
quarkus-mongodb-client
+
+ io.quarkus
+ quarkus-opentelemetry-deployment
+ true
+
org.testcontainers
mongodb
diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/ContextProviderBuildItem.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/ContextProviderBuildItem.java
new file mode 100644
index 0000000000000..162e75418e5b7
--- /dev/null
+++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/ContextProviderBuildItem.java
@@ -0,0 +1,23 @@
+package io.quarkus.mongodb.deployment;
+
+import java.util.List;
+
+import com.mongodb.reactivestreams.client.ReactiveContextProvider;
+
+import io.quarkus.builder.item.SimpleBuildItem;
+
+/**
+ * Register additional {@link ReactiveContextProvider}s for the MongoDB clients.
+ */
+public final class ContextProviderBuildItem extends SimpleBuildItem {
+
+ private final List classNames;
+
+ public ContextProviderBuildItem(List classNames) {
+ this.classNames = classNames == null ? List.of() : classNames;
+ }
+
+ public List getContextProviderClassNames() {
+ return classNames;
+ }
+}
diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java
index 767e3832ce4df..9abfbcde5d5f7 100644
--- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java
+++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java
@@ -32,6 +32,7 @@
import com.mongodb.client.model.changestream.UpdateDescription;
import com.mongodb.event.CommandListener;
import com.mongodb.event.ConnectionPoolListener;
+import com.mongodb.reactivestreams.client.ReactiveContextProvider;
import com.mongodb.spi.dns.DnsClientProvider;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
@@ -69,6 +70,7 @@
import io.quarkus.mongodb.runtime.MongoClientRecorder;
import io.quarkus.mongodb.runtime.MongoClientSupport;
import io.quarkus.mongodb.runtime.MongoClients;
+import io.quarkus.mongodb.runtime.MongoReactiveContextProvider;
import io.quarkus.mongodb.runtime.MongoServiceBindingConverter;
import io.quarkus.mongodb.runtime.MongodbConfig;
import io.quarkus.mongodb.runtime.dns.MongoDnsClient;
@@ -113,9 +115,11 @@ AdditionalIndexedClassesBuildItem includeDnsTypesToIndex() {
}
@BuildStep
- AdditionalIndexedClassesBuildItem includeDnsTypesToIndex(MongoClientBuildTimeConfig buildTimeConfig) {
+ AdditionalIndexedClassesBuildItem includeMongoCommandListener(MongoClientBuildTimeConfig buildTimeConfig) {
if (buildTimeConfig.tracingEnabled) {
- return new AdditionalIndexedClassesBuildItem(MongoTracingCommandListener.class.getName());
+ return new AdditionalIndexedClassesBuildItem(
+ MongoTracingCommandListener.class.getName(),
+ MongoReactiveContextProvider.class.getName());
}
return new AdditionalIndexedClassesBuildItem();
}
@@ -161,15 +165,27 @@ CommandListenerBuildItem collectCommandListeners(CombinedIndexBuildItem indexBui
return new CommandListenerBuildItem(names);
}
+ @BuildStep
+ ContextProviderBuildItem collectContextProviders(CombinedIndexBuildItem indexBuildItem) {
+ Collection contextProviders = indexBuildItem.getIndex()
+ .getAllKnownImplementors(DotName.createSimple(ReactiveContextProvider.class.getName()));
+ List names = contextProviders.stream()
+ .map(ci -> ci.name().toString())
+ .collect(Collectors.toList());
+ return new ContextProviderBuildItem(names);
+ }
+
@BuildStep
List addExtensionPointsToNative(CodecProviderBuildItem codecProviders,
PropertyCodecProviderBuildItem propertyCodecProviders, BsonDiscriminatorBuildItem bsonDiscriminators,
- CommandListenerBuildItem commandListeners) {
+ CommandListenerBuildItem commandListeners,
+ ContextProviderBuildItem contextProviders) {
List reflectiveClassNames = new ArrayList<>();
reflectiveClassNames.addAll(codecProviders.getCodecProviderClassNames());
reflectiveClassNames.addAll(propertyCodecProviders.getPropertyCodecProviderClassNames());
reflectiveClassNames.addAll(bsonDiscriminators.getBsonDiscriminatorClassNames());
reflectiveClassNames.addAll(commandListeners.getCommandListenerClassNames());
+ reflectiveClassNames.addAll(contextProviders.getContextProviderClassNames());
List reflectiveClass = reflectiveClassNames.stream()
.map(s -> ReflectiveClassBuildItem.builder(s).methods().build())
@@ -256,6 +272,7 @@ void build(
PropertyCodecProviderBuildItem propertyCodecProvider,
BsonDiscriminatorBuildItem bsonDiscriminator,
CommandListenerBuildItem commandListener,
+ ContextProviderBuildItem contextProvider,
List connectionPoolListenerProvider,
BuildProducer additionalBeanBuildItemProducer,
BuildProducer syntheticBeanBuildItemBuildProducer) {
@@ -277,6 +294,9 @@ void build(
for (String name : commandListener.getCommandListenerClassNames()) {
additionalBeansBuilder.addBeanClass(name);
}
+ for (String name : contextProvider.getContextProviderClassNames()) {
+ additionalBeansBuilder.addBeanClass(name);
+ }
additionalBeanBuildItemProducer.produce(additionalBeansBuilder.build());
// create MongoClientSupport as a synthetic bean as it's used in AbstractMongoClientProducer
diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MockReactiveContextProvider.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MockReactiveContextProvider.java
new file mode 100644
index 0000000000000..7f85aa84d3d59
--- /dev/null
+++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MockReactiveContextProvider.java
@@ -0,0 +1,22 @@
+package io.quarkus.mongodb;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.reactivestreams.Subscriber;
+
+import com.mongodb.RequestContext;
+import com.mongodb.reactivestreams.client.ReactiveContextProvider;
+
+import io.quarkus.mongodb.runtime.MongoRequestContext;
+
+public class MockReactiveContextProvider implements ReactiveContextProvider {
+
+ public static final List EVENTS = new ArrayList<>();
+
+ @Override
+ public RequestContext getContext(Subscriber> subscriber) {
+ EVENTS.add(MongoRequestContext.class.getName());
+ return new MongoRequestContext(null);
+ }
+}
diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTracingEnabled.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTracingEnabled.java
new file mode 100644
index 0000000000000..e1a105f7b0d3b
--- /dev/null
+++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTracingEnabled.java
@@ -0,0 +1,49 @@
+package io.quarkus.mongodb;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+import java.time.Duration;
+
+import jakarta.inject.Inject;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.mongodb.reactive.ReactiveMongoClient;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class MongoTracingEnabled extends MongoTestBase {
+
+ @Inject
+ ReactiveMongoClient reactiveClient;
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer(
+ () -> ShrinkWrap.create(JavaArchive.class)
+ .addClasses(MongoTestBase.class, MockReactiveContextProvider.class, MockCommandListener.class))
+ .withConfigurationResource("application-tracing-mongoclient.properties");
+
+ @AfterEach
+ void cleanup() {
+ if (reactiveClient != null) {
+ reactiveClient.close();
+ }
+ }
+
+ @Test
+ void invokeReactiveContextProvider() {
+ String dbNames = reactiveClient.listDatabaseNames().toUni().await().atMost(Duration.ofSeconds(30L));
+ assertThat(dbNames).as("expect db names available").isNotBlank();
+ await().atMost(Duration.ofSeconds(30L)).untilAsserted(
+ () -> assertThat(MockReactiveContextProvider.EVENTS)
+ .as("reactive context provider must be called")
+ .isNotEmpty());
+ assertThat(MockCommandListener.EVENTS).isNotEmpty();
+
+ }
+
+}
diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTracingNotEnabledTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTracingNotEnabledTest.java
new file mode 100644
index 0000000000000..09ac8a61f34dc
--- /dev/null
+++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTracingNotEnabledTest.java
@@ -0,0 +1,42 @@
+package io.quarkus.mongodb;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import jakarta.inject.Inject;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.mongodb.client.MongoClient;
+
+import io.quarkus.test.QuarkusUnitTest;
+
+public class MongoTracingNotEnabledTest extends MongoTestBase {
+
+ @Inject
+ MongoClient client;
+
+ @RegisterExtension
+ static final QuarkusUnitTest config = new QuarkusUnitTest()
+ .setArchiveProducer(
+ () -> ShrinkWrap.create(JavaArchive.class).addClasses(MongoTestBase.class,
+ MockReactiveContextProvider.class))
+ .withConfigurationResource("default-mongoclient.properties");
+
+ @AfterEach
+ void cleanup() {
+ if (client != null) {
+ client.close();
+ }
+ }
+
+ @Test
+ void contextProviderMustNotBeCalledIfNoOpenTelemetryIsAvailable() {
+ assertThat(client.listDatabaseNames().first()).isNotEmpty();
+ assertThat(MockReactiveContextProvider.EVENTS).isEmpty();
+ }
+
+}
diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/deployment/ContextProviderBuildItemTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/deployment/ContextProviderBuildItemTest.java
new file mode 100644
index 0000000000000..d813fda475164
--- /dev/null
+++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/deployment/ContextProviderBuildItemTest.java
@@ -0,0 +1,28 @@
+package io.quarkus.mongodb.deployment;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+class ContextProviderBuildItemTest {
+
+ @Test
+ void getContextProviderClassNames() {
+ ContextProviderBuildItem item = new ContextProviderBuildItem(List.of("foo.bar"));
+ assertThat(item.getContextProviderClassNames())
+ .hasSize(1)
+ .first()
+ .isEqualTo("foo.bar");
+ }
+
+ @Test
+ void emptyOrNull() {
+ ContextProviderBuildItem withNull = new ContextProviderBuildItem(null);
+ assertThat(withNull.getContextProviderClassNames()).isEmpty();
+
+ ContextProviderBuildItem empty = new ContextProviderBuildItem(List.of());
+ assertThat(empty.getContextProviderClassNames()).isEmpty();
+ }
+}
diff --git a/extensions/mongodb-client/deployment/src/test/resources/application-tracing-mongoclient.properties b/extensions/mongodb-client/deployment/src/test/resources/application-tracing-mongoclient.properties
new file mode 100644
index 0000000000000..32e6f84c16416
--- /dev/null
+++ b/extensions/mongodb-client/deployment/src/test/resources/application-tracing-mongoclient.properties
@@ -0,0 +1,3 @@
+quarkus.mongodb.connection-string=mongodb://127.0.0.1:27018
+quarkus.mongodb.tracing.enabled=true
+
diff --git a/extensions/mongodb-client/runtime/pom.xml b/extensions/mongodb-client/runtime/pom.xml
index 408d5efd30210..cbdc90f2750c3 100644
--- a/extensions/mongodb-client/runtime/pom.xml
+++ b/extensions/mongodb-client/runtime/pom.xml
@@ -58,6 +58,11 @@
quarkus-kubernetes-service-binding
true
+
+ io.quarkus
+ quarkus-opentelemetry
+ true
+
diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java
index cc0b938767c99..125f978669dfb 100644
--- a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java
+++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoClients.java
@@ -56,6 +56,7 @@
import com.mongodb.connection.SslSettings;
import com.mongodb.event.CommandListener;
import com.mongodb.event.ConnectionPoolListener;
+import com.mongodb.reactivestreams.client.ReactiveContextProvider;
import io.quarkus.arc.Arc;
import io.quarkus.arc.InstanceHandle;
@@ -86,18 +87,21 @@ public class MongoClients {
private final Map mongoclients = new HashMap<>();
private final Map reactiveMongoClients = new HashMap<>();
+ private final Instance reactiveContextProviders;
private final Instance customizers;
public MongoClients(MongodbConfig mongodbConfig, MongoClientSupport mongoClientSupport,
Instance codecProviders,
Instance propertyCodecProviders,
Instance commandListeners,
+ Instance reactiveContextProviders,
@Any Instance customizers) {
this.mongodbConfig = mongodbConfig;
this.mongoClientSupport = mongoClientSupport;
this.codecProviders = codecProviders;
this.propertyCodecProviders = propertyCodecProviders;
this.commandListeners = commandListeners;
+ this.reactiveContextProviders = reactiveContextProviders;
this.customizers = customizers;
try {
@@ -121,7 +125,8 @@ public MongoClients(MongodbConfig mongodbConfig, MongoClientSupport mongoClientS
}
public MongoClient createMongoClient(String clientName) throws MongoException {
- MongoClientSettings mongoConfiguration = createMongoConfiguration(clientName, getMatchingMongoClientConfig(clientName));
+ MongoClientSettings mongoConfiguration = createMongoConfiguration(clientName, getMatchingMongoClientConfig(clientName),
+ false);
MongoClient client = com.mongodb.client.MongoClients.create(mongoConfiguration);
mongoclients.put(clientName, client);
return client;
@@ -129,7 +134,8 @@ public MongoClient createMongoClient(String clientName) throws MongoException {
public ReactiveMongoClient createReactiveMongoClient(String clientName)
throws MongoException {
- MongoClientSettings mongoConfiguration = createMongoConfiguration(clientName, getMatchingMongoClientConfig(clientName));
+ MongoClientSettings mongoConfiguration = createMongoConfiguration(clientName, getMatchingMongoClientConfig(clientName),
+ true);
com.mongodb.reactivestreams.client.MongoClient client = com.mongodb.reactivestreams.client.MongoClients
.create(mongoConfiguration);
ReactiveMongoClientImpl reactive = new ReactiveMongoClientImpl(client);
@@ -254,7 +260,7 @@ public void apply(ServerSettings.Builder builder) {
}
}
- private MongoClientSettings createMongoConfiguration(String name, MongoClientConfig config) {
+ private MongoClientSettings createMongoConfiguration(String name, MongoClientConfig config, boolean isReactive) {
if (config == null) {
throw new RuntimeException("mongo config is missing for creating mongo client.");
}
@@ -262,6 +268,10 @@ private MongoClientSettings createMongoConfiguration(String name, MongoClientCon
MongoClientSettings.Builder settings = MongoClientSettings.builder();
+ if (isReactive) {
+ reactiveContextProviders.stream().findAny().ifPresent(settings::contextProvider);
+ }
+
ConnectionString connectionString;
Optional maybeConnectionString = config.connectionString;
if (maybeConnectionString.isPresent()) {
diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoReactiveContextProvider.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoReactiveContextProvider.java
new file mode 100644
index 0000000000000..4061c18e2007e
--- /dev/null
+++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoReactiveContextProvider.java
@@ -0,0 +1,17 @@
+package io.quarkus.mongodb.runtime;
+
+import org.reactivestreams.Subscriber;
+
+import com.mongodb.RequestContext;
+import com.mongodb.reactivestreams.client.ReactiveContextProvider;
+
+import io.opentelemetry.context.Context;
+
+public class MongoReactiveContextProvider implements ReactiveContextProvider {
+
+ @Override
+ public RequestContext getContext(Subscriber> subscriber) {
+ return new MongoRequestContext(Context.current());
+ }
+
+}
diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoRequestContext.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoRequestContext.java
new file mode 100644
index 0000000000000..cd135be5973f9
--- /dev/null
+++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/runtime/MongoRequestContext.java
@@ -0,0 +1,57 @@
+package io.quarkus.mongodb.runtime;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
+
+import com.mongodb.RequestContext;
+
+import io.opentelemetry.context.Context;
+
+@SuppressWarnings("unchecked")
+public class MongoRequestContext implements RequestContext {
+ public static final String OTEL_CONTEXT_KEY = "otel.context.current";
+ private final Map