diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 2a354f086715c..93fe0b51bf8d0 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -26,6 +26,7 @@ 0.2.0 0.0.12 0.1.13 + 0.1.5 0.34.3 3.0.18.Final 1.0.0.Final @@ -2678,6 +2679,11 @@ opentracing-kafka-client ${opentracing-kafka.version} + + io.opentracing.contrib + opentracing-mongo-common + ${opentracing-mongo.version} + io.rest-assured rest-assured diff --git a/docs/src/main/asciidoc/mongodb.adoc b/docs/src/main/asciidoc/mongodb.adoc index 54fe32c7fe5a0..7d10df91b2b66 100644 --- a/docs/src/main/asciidoc/mongodb.adoc +++ b/docs/src/main/asciidoc/mongodb.adoc @@ -596,6 +596,12 @@ 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 link:microprofile-metrics[SmallRye Metrics], connection pool metrics will be available under the `vendor` scope. +== Tracing + +If you are using the `quarkus-smallrye-opentracing` extension, `quarkus-mongodb-client` can register traces about the commands executed. +This behavior must be enabled by setting the `quarkus.mongodb.tracing.enabled` property to `true` in your `application.properties` and adding the dependency `io.opentracing.contrib:opentracing-mongo-common` to your pom.xml (for more info read the link:opentracing#mongodb-client[OpenTracing - MongoDB client] section). + +Read the link:opentracing[OpenTracing] guide, for how to configure OpenTracing and how to use the Jaeger tracer. == The legacy client diff --git a/docs/src/main/asciidoc/opentracing.adoc b/docs/src/main/asciidoc/opentracing.adoc index b3a2f67c293b5..ea5f10fea3ed6 100644 --- a/docs/src/main/asciidoc/opentracing.adoc +++ b/docs/src/main/asciidoc/opentracing.adoc @@ -257,6 +257,28 @@ mp.messaging.incoming.prices.interceptor.classes=io.opentracing.contrib.kafka.Tr NOTE: `interceptor.classes` accept a list of classes separated by a comma. + +=== MongoDB client + +The https://github.com/opentracing-contrib/java-mongo-driver[Mongo Driver instrumentation] will add a span for each command executed by your application. To enable it, add the following dependency to your pom.xml: + +[source, xml] +---- + + io.opentracing.contrib + opentracing-mongo-common + +---- + +It contains the OpenTracing CommandListener that will be registered on the configuration of the mongo client. +Following the link:mongodb[MongoDB guide], the command listener will be registered defining the config property as follows: + +[source, properties] +---- +# Enable tracing commands in mongodb client +quarkus.mongodb.tracing.enabled=true +---- + [[configuration-reference]] == Jaeger Configuration Reference diff --git a/extensions/mongodb-client/deployment/pom.xml b/extensions/mongodb-client/deployment/pom.xml index 9750bfa790fcd..efd33d5fb0196 100644 --- a/extensions/mongodb-client/deployment/pom.xml +++ b/extensions/mongodb-client/deployment/pom.xml @@ -64,6 +64,27 @@ quarkus-resteasy-deployment test + + io.quarkus + quarkus-smallrye-opentracing-deployment + test + + + io.opentracing.contrib + opentracing-mongo-common + test + + + io.opentracing + opentracing-mock + test + + + io.opentracing + opentracing-util + test-jar + test + diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java index 166893f250f81..f6c4a7d58d251 100644 --- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientBuildTimeConfig.java @@ -18,6 +18,12 @@ public class MongoClientBuildTimeConfig { @ConfigItem(name = "metrics.enabled") public boolean metricsEnabled; + /** + * Whether or not tracing spans of driver commands are sent in case the smallrye-opentracing extension is present. + */ + @ConfigItem(name = "tracing.enabled") + public boolean tracingEnabled; + /** * If set to true, the default clients will always be created even if there are no injection points that use them */ 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 e3e7c99f4de3e..72ee208bd7b3e 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 @@ -35,6 +35,8 @@ import io.quarkus.arc.processor.BuildExtension; import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.InjectionPointInfo; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -59,6 +61,7 @@ import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; public class MongoClientProcessor { + private static final String MONGODB_TRACING_COMMANDLISTENER_CLASSNAME = "io.quarkus.mongodb.tracing.MongoTracingCommandListener"; private static final DotName LEGACY_MONGO_CLIENT_ANNOTATION = DotName .createSimple(io.quarkus.mongodb.runtime.MongoClientName.class.getName()); private static final DotName MONGO_CLIENT_ANNOTATION = DotName.createSimple(MongoClientName.class.getName()); @@ -93,10 +96,16 @@ BsonDiscriminatorBuildItem collectBsonDiscriminators(CombinedIndexBuildItem inde } @BuildStep - CommandListenerBuildItem collectCommandListeners(CombinedIndexBuildItem indexBuildItem) { + CommandListenerBuildItem collectCommandListeners(CombinedIndexBuildItem indexBuildItem, + MongoClientBuildTimeConfig buildTimeConfig, Capabilities capabilities) { Collection commandListenerClasses = indexBuildItem.getIndex() .getAllKnownImplementors(DotName.createSimple(CommandListener.class.getName())); - List names = commandListenerClasses.stream().map(ci -> ci.name().toString()).collect(Collectors.toList()); + List names = commandListenerClasses.stream() + .map(ci -> ci.name().toString()) + .collect(Collectors.toList()); + if (buildTimeConfig.tracingEnabled && capabilities.isPresent(Capability.OPENTRACING)) { + names.add(MONGODB_TRACING_COMMANDLISTENER_CLASSNAME); + } return new CommandListenerBuildItem(names); } diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTracingCommandListenerTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTracingCommandListenerTest.java new file mode 100644 index 0000000000000..05d872f5b6838 --- /dev/null +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoTracingCommandListenerTest.java @@ -0,0 +1,73 @@ +package io.quarkus.mongodb; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.mongodb.client.MongoClient; + +import io.opentracing.mock.MockSpan; +import io.opentracing.mock.MockTracer; +import io.opentracing.util.GlobalTracer; +import io.opentracing.util.GlobalTracerTestUtil; +import io.quarkus.mongodb.tracing.MongoTracingCommandListener; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Test the inclusion and config of the {@link MongoTracingCommandListener}. + * + * @see io.quarkus.smallrye.opentracing.deployment.TracingTest + */ +public class MongoTracingCommandListenerTest extends MongoTestBase { + + @Inject + MongoClient client; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class).addClasses(MongoTestBase.class)) + .withConfigurationResource("application-tracing-mongo.properties"); + + static MockTracer mockTracer = new MockTracer(); + static { + GlobalTracer.register(mockTracer); + } + + @BeforeEach + public void before() { + mockTracer.reset(); + } + + @AfterAll + public static void afterAll() { + GlobalTracerTestUtil.resetGlobalTracer(); + } + + @AfterEach + void cleanup() { + if (client != null) { + client.close(); + } + } + + @Test + void testClientInitialization() { + assertThat(mockTracer.finishedSpans()).isEmpty(); + + assertThat(client.listDatabaseNames().first()).isNotEmpty(); + + assertThat(mockTracer.finishedSpans()).hasSize(1); + MockSpan span = mockTracer.finishedSpans().get(0); + assertThat(span.operationName()).isEqualTo("listDatabases"); + } + +} diff --git a/extensions/mongodb-client/deployment/src/test/resources/application-tracing-mongo.properties b/extensions/mongodb-client/deployment/src/test/resources/application-tracing-mongo.properties new file mode 100644 index 0000000000000..fc4f686e65cd5 --- /dev/null +++ b/extensions/mongodb-client/deployment/src/test/resources/application-tracing-mongo.properties @@ -0,0 +1,8 @@ +quarkus.mongodb.connection-string=mongodb://localhost:27018 +quarkus.mongodb.tracing.enabled=true + + +quarkus.jaeger.enabled=true +quarkus.jaeger.service-name=tracing-test +quarkus.jaeger.sampler-param=1 +quarkus.jaeger.sampler-type=const \ No newline at end of file diff --git a/extensions/mongodb-client/runtime/pom.xml b/extensions/mongodb-client/runtime/pom.xml index 83005556aeebd..a855d1c01b6b2 100644 --- a/extensions/mongodb-client/runtime/pom.xml +++ b/extensions/mongodb-client/runtime/pom.xml @@ -63,6 +63,17 @@ true + + io.quarkus + quarkus-smallrye-opentracing + true + + + io.opentracing.contrib + opentracing-mongo-common + true + + org.graalvm.nativeimage svm diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/tracing/MongoTracingCommandListener.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/tracing/MongoTracingCommandListener.java new file mode 100644 index 0000000000000..b43418b792333 --- /dev/null +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/tracing/MongoTracingCommandListener.java @@ -0,0 +1,46 @@ +package io.quarkus.mongodb.tracing; + +import com.mongodb.event.CommandFailedEvent; +import com.mongodb.event.CommandListener; +import com.mongodb.event.CommandStartedEvent; +import com.mongodb.event.CommandSucceededEvent; + +import io.opentracing.contrib.mongo.common.TracingCommandListener; +import io.opentracing.util.GlobalTracer; +import io.vertx.core.logging.Logger; +import io.vertx.core.logging.LoggerFactory; + +/** + * Command Listener for Mongo client delegated to {@link TracingCommandListener}. + * + */ +public class MongoTracingCommandListener implements CommandListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(MongoTracingCommandListener.class); + + private TracingCommandListener delegate; + + public MongoTracingCommandListener() { + this.delegate = new TracingCommandListener.Builder(GlobalTracer.get()).build(); + LOGGER.debug("TracingCommandListener Delegate created"); + } + + @Override + public void commandStarted(CommandStartedEvent event) { + LOGGER.trace("commandStarted event " + event.getCommandName()); + delegate.commandStarted(event); + } + + @Override + public void commandFailed(CommandFailedEvent event) { + LOGGER.trace("commandFailed event " + event.getCommandName()); + delegate.commandFailed(event); + } + + @Override + public void commandSucceeded(CommandSucceededEvent event) { + LOGGER.trace("commandSucceded event " + event.getCommandName()); + delegate.commandSucceeded(event); + } + +}