From b416ece9c30a088ee48f3f7e3cca085a00e89ff0 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 14 Apr 2021 13:51:21 +0900 Subject: [PATCH] Extract Mongo library instrumentation (#2789) * Extract mongo library instrumentation * Finish * Drift * Cleanup * build twice * Spot --- .../api/tracer/DatabaseClientTracer.java | 2 + .../javaagent/mongo-3.1-javaagent.gradle | 5 +- .../MongoClientInstrumentationModule.java | 5 +- .../v3_1/MongoInstrumentationSingletons.java | 18 ++ .../src/test/groovy/MongoClientTest.groovy | 163 +--------------- .../library/mongo-3.1-library.gradle | 8 + .../mongo/v3_1}/MongoClientTracer.java | 51 +++-- .../mongo/v3_1/MongoTracing.java | 37 ++++ .../mongo/v3_1/MongoTracingBuilder.java | 37 ++++ .../mongo/v3_1}/TracingCommandListener.java | 20 +- .../mongo/v3_1/MongoClientTest.groovy | 16 ++ .../mongo/v3_1}/MongoClientTracerTest.groovy | 17 +- .../testing/mongo-3.1-testing.gradle | 11 ++ .../v3_1/AbstractMongo31ClientTest.groovy | 184 ++++++++++++++++++ .../javaagent/mongo-3.7-javaagent.gradle | 3 +- .../MongoClientInstrumentationModule.java | 5 +- .../v3_7/MongoInstrumentationSingletons.java | 18 ++ .../src/test/groovy/MongoClientTest.groovy | 17 +- .../javaagent/mongo-4.0-javaagent.gradle | 2 +- .../MongoClientInstrumentationModule.java | 5 +- .../v4_0/MongoInstrumentationSingletons.java | 18 ++ .../groovy/Mongo4ReactiveClientTest.groovy | 18 +- .../src/test/groovy/MongoClientTest.groovy | 18 +- .../mongo-async-3.3-javaagent.gradle | 3 +- ...MongoAsyncClientInstrumentationModule.java | 5 +- .../v3_3/MongoInstrumentationSingletons.java | 18 ++ .../test/groovy/MongoAsyncClientTest.groovy | 17 +- .../javaagent/mongo-common-javaagent.gradle | 19 -- .../testing}/AbstractMongoClientTest.groovy | 31 ++- settings.gradle | 3 +- 30 files changed, 534 insertions(+), 240 deletions(-) create mode 100644 instrumentation/mongo/mongo-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_1/MongoInstrumentationSingletons.java create mode 100644 instrumentation/mongo/mongo-3.1/library/mongo-3.1-library.gradle rename instrumentation/mongo/{mongo-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo => mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1}/MongoClientTracer.java (86%) create mode 100644 instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTracing.java create mode 100644 instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTracingBuilder.java rename instrumentation/mongo/{mongo-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo => mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1}/TracingCommandListener.java (62%) create mode 100644 instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.groovy rename instrumentation/mongo/{mongo-common/javaagent/src/test/groovy => mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1}/MongoClientTracerTest.groovy (79%) create mode 100644 instrumentation/mongo/mongo-3.1/testing/mongo-3.1-testing.gradle create mode 100644 instrumentation/mongo/mongo-3.1/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.groovy create mode 100644 instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoInstrumentationSingletons.java create mode 100644 instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoInstrumentationSingletons.java create mode 100644 instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoInstrumentationSingletons.java delete mode 100644 instrumentation/mongo/mongo-common/javaagent/mongo-common-javaagent.gradle rename instrumentation/mongo/mongo-testing/src/main/groovy/{ => io/opentelemetry/instrumentation/mongo/testing}/AbstractMongoClientTest.groovy (91%) diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/DatabaseClientTracer.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/DatabaseClientTracer.java index ad73160661..7e000e1188 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/DatabaseClientTracer.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/DatabaseClientTracer.java @@ -121,6 +121,7 @@ public abstract class DatabaseClientTracer commandListeners) { for (CommandListener commandListener : commandListeners) { - if (commandListener instanceof TracingCommandListener) { + if (commandListener == MongoInstrumentationSingletons.LISTENER) { return; } } - builder.addCommandListener(new TracingCommandListener()); + builder.addCommandListener(MongoInstrumentationSingletons.LISTENER); } } } diff --git a/instrumentation/mongo/mongo-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_1/MongoInstrumentationSingletons.java b/instrumentation/mongo/mongo-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_1/MongoInstrumentationSingletons.java new file mode 100644 index 0000000000..4299899a04 --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_1/MongoInstrumentationSingletons.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v3_1; + +import com.mongodb.event.CommandListener; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.mongo.v3_1.MongoTracing; + +public final class MongoInstrumentationSingletons { + + public static final CommandListener LISTENER = + MongoTracing.create(GlobalOpenTelemetry.get()).newCommandListener(); + + private MongoInstrumentationSingletons() {} +} diff --git a/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy b/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy index 8d1bd6d876..8f8289d56d 100644 --- a/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy +++ b/instrumentation/mongo/mongo-3.1/javaagent/src/test/groovy/MongoClientTest.groovy @@ -3,167 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import static io.opentelemetry.instrumentation.test.utils.PortUtils.UNUSABLE_PORT -import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace - -import com.mongodb.MongoClient import com.mongodb.MongoClientOptions -import com.mongodb.MongoTimeoutException -import com.mongodb.ServerAddress -import com.mongodb.client.MongoCollection -import com.mongodb.client.MongoDatabase -import org.bson.BsonDocument -import org.bson.BsonString -import org.bson.Document -import spock.lang.Shared - -class MongoClientTest extends AbstractMongoClientTest> { - - @Shared - MongoClient client - - def setupSpec() throws Exception { - client = new MongoClient(new ServerAddress("localhost", port), - MongoClientOptions.builder() - .description("some-description") - .build()) - } - - def cleanupSpec() throws Exception { - client?.close() - client = null - } +import io.opentelemetry.instrumentation.mongo.v3_1.AbstractMongo31ClientTest +import io.opentelemetry.instrumentation.test.AgentTestTrait +class MongoClientTest extends AbstractMongo31ClientTest implements AgentTestTrait { @Override - void createCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionNoDescription(String dbName, String collectionName) { - MongoDatabase db = new MongoClient("localhost", port).getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { - def clientOptions = client.mongoClientOptions - def newClientOptions = MongoClientOptions.builder(clientOptions).build() - MongoDatabase db = new MongoClient(new ServerAddress("localhost", port), newClientOptions).getDatabase(dbName) - db.createCollection(collectionName) - } - - @Override - int getCollection(String dbName, String collectionName) { - MongoDatabase db = client.getDatabase(dbName) - return db.getCollection(collectionName).count() - } - - @Override - MongoCollection setupInsert(String dbName, String collectionName) { - MongoCollection collection = runUnderTrace("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int insert(MongoCollection collection) { - collection.insertOne(new Document("password", "SECRET")) - return collection.count() - } - - @Override - MongoCollection setupUpdate(String dbName, String collectionName) { - MongoCollection collection = runUnderTrace("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - def coll = db.getCollection(collectionName) - coll.insertOne(new Document("password", "OLDPW")) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int update(MongoCollection collection) { - def result = collection.updateOne( - new BsonDocument("password", new BsonString("OLDPW")), - new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) - collection.count() - return result.modifiedCount - } - - @Override - MongoCollection setupDelete(String dbName, String collectionName) { - MongoCollection collection = runUnderTrace("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - def coll = db.getCollection(collectionName) - coll.insertOne(new Document("password", "SECRET")) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - int delete(MongoCollection collection) { - def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) - collection.count() - return result.deletedCount - } - - @Override - MongoCollection setupGetMore(String dbName, String collectionName) { - MongoCollection collection = runUnderTrace("setup") { - MongoDatabase db = client.getDatabase(dbName) - def coll = db.getCollection(collectionName) - coll.insertMany([new Document("_id", 0), new Document("_id", 1), new Document("_id", 2)]) - return coll - } - ignoreTracesAndClear(1) - return collection - } - - @Override - void getMore(MongoCollection collection) { - collection.find().filter(new Document("_id", new Document('$gte', 0))) - .batchSize(2).into(new ArrayList()) - } - - @Override - void error(String dbName, String collectionName) { - MongoCollection collection = runUnderTrace("setup") { - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - return db.getCollection(collectionName) - } - ignoreTracesAndClear(1) - collection.updateOne(new BsonDocument(), new BsonDocument()) - } - - def "test client failure"() { - setup: - def options = MongoClientOptions.builder().serverSelectionTimeout(10).build() - def client = new MongoClient(new ServerAddress("localhost", UNUSABLE_PORT), [], options) - - when: - MongoDatabase db = client.getDatabase(dbName) - db.createCollection(collectionName) - - then: - thrown(MongoTimeoutException) - // Unfortunately not caught by our instrumentation. - assertTraces(0) {} - - where: - dbName = "test_db" - collectionName = createCollectionName() + void configureMongoClientOptions(MongoClientOptions.Builder options) { } } diff --git a/instrumentation/mongo/mongo-3.1/library/mongo-3.1-library.gradle b/instrumentation/mongo/mongo-3.1/library/mongo-3.1-library.gradle new file mode 100644 index 0000000000..c37d058a38 --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/library/mongo-3.1-library.gradle @@ -0,0 +1,8 @@ +apply from: "$rootDir/gradle/instrumentation-library.gradle" +apply plugin: "net.ltgt.errorprone" + +dependencies { + library group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0' + + testImplementation project(':instrumentation:mongo:mongo-3.1:testing') +} \ No newline at end of file diff --git a/instrumentation/mongo/mongo-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/MongoClientTracer.java b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTracer.java similarity index 86% rename from instrumentation/mongo/mongo-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/MongoClientTracer.java rename to instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTracer.java index cf8ab89633..9319c48d61 100644 --- a/instrumentation/mongo/mongo-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/MongoClientTracer.java +++ b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTracer.java @@ -3,13 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.mongo; +package io.opentelemetry.instrumentation.mongo.v3_1; import static java.util.Arrays.asList; import com.mongodb.ServerAddress; import com.mongodb.connection.ConnectionDescription; import com.mongodb.event.CommandStartedEvent; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes; @@ -29,46 +30,49 @@ import org.bson.BsonDocument; import org.bson.BsonValue; import org.bson.json.JsonWriter; import org.bson.json.JsonWriterSettings; +import org.checkerframework.checker.nullness.qual.Nullable; -public class MongoClientTracer +final class MongoClientTracer extends DatabaseClientTracer { - private static final MongoClientTracer TRACER = new MongoClientTracer(); private final int maxNormalizedQueryLength; - private final JsonWriterSettings jsonWriterSettings; + @Nullable private final JsonWriterSettings jsonWriterSettings; - public MongoClientTracer() { - this(32 * 1024); - } - - public MongoClientTracer(int maxNormalizedQueryLength) { - super(NetPeerAttributes.INSTANCE); + MongoClientTracer(OpenTelemetry openTelemetry, int maxNormalizedQueryLength) { + super(openTelemetry, NetPeerAttributes.INSTANCE); this.maxNormalizedQueryLength = maxNormalizedQueryLength; this.jsonWriterSettings = createJsonWriterSettings(maxNormalizedQueryLength); } - public static MongoClientTracer tracer() { - return TRACER; - } - @Override protected String getInstrumentationName() { return "io.opentelemetry.javaagent.mongo-common"; } @Override + // TODO(anuraaga): Migrate off of StringWriter to avoid synchronization. + @SuppressWarnings("JdkObsolete") protected String sanitizeStatement(BsonDocument command) { StringWriter stringWriter = new StringWriter(128); - writeScrubbed(command, new JsonWriter(stringWriter, jsonWriterSettings), true); + // jsonWriterSettings is generally not null but could be due to security manager or unknown + // API incompatibilities, which we can't detect by Muzzle because we use reflection. + JsonWriter jsonWriter = + jsonWriterSettings != null + ? new JsonWriter(stringWriter, jsonWriterSettings) + : new JsonWriter(stringWriter); + writeScrubbed(command, jsonWriter, true); // If using MongoDB driver >= 3.7, the substring invocation will be a no-op due to use of // JsonWriterSettings.Builder.maxLength in the static initializer for JSON_WRITER_SETTINGS - return stringWriter - .getBuffer() - .substring(0, Math.min(maxNormalizedQueryLength, stringWriter.getBuffer().length())); + StringBuffer buf = stringWriter.getBuffer(); + if (buf.length() <= maxNormalizedQueryLength) { + return buf.toString(); + } + return buf.substring(0, maxNormalizedQueryLength); } @Override - public String spanName(CommandStartedEvent event, BsonDocument document, String normalizedQuery) { + protected String spanName( + CommandStartedEvent event, BsonDocument document, String normalizedQuery) { return conventionSpanName(dbName(event), event.getCommandName(), collectionName(event)); } @@ -92,6 +96,7 @@ public class MongoClientTracer } @Override + @Nullable protected String dbConnectionString(CommandStartedEvent event) { ConnectionDescription connectionDescription = event.getConnectionDescription(); if (connectionDescription != null) { @@ -109,6 +114,7 @@ public class MongoClientTracer } @Override + @Nullable protected InetSocketAddress peerAddress(CommandStartedEvent event) { if (event.getConnectionDescription() != null && event.getConnectionDescription().getServerAddress() != null) { @@ -130,7 +136,7 @@ public class MongoClientTracer return event.getCommandName(); } - private static final Method IS_TRUNCATED_METHOD; + @Nullable private static final Method IS_TRUNCATED_METHOD; static { IS_TRUNCATED_METHOD = @@ -140,6 +146,7 @@ public class MongoClientTracer .orElse(null); } + @Nullable private JsonWriterSettings createJsonWriterSettings(int maxNormalizedQueryLength) { JsonWriterSettings settings = null; try { @@ -175,14 +182,17 @@ public class MongoClientTracer builderClass.getMethod("build", (Class[]) null).invoke(builder, (Object[]) null); } } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) { + // Ignore } if (settings == null) { try { + // Constructor removed in 4.0+ so use reflection. 4.0+ will have used the builder above. settings = JsonWriterSettings.class.getConstructor(Boolean.TYPE).newInstance(false); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) { + // Ignore } } @@ -264,6 +274,7 @@ public class MongoClientTracer "createIndexes", "listIndexes")); + @Nullable private static String collectionName(CommandStartedEvent event) { if (event.getCommandName().equals("getMore")) { if (event.getCommand().containsKey("collection")) { diff --git a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTracing.java b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTracing.java new file mode 100644 index 0000000000..f4ac61dab6 --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTracing.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.mongo.v3_1; + +import com.mongodb.event.CommandListener; +import io.opentelemetry.api.OpenTelemetry; + +/** Entrypoint to OpenTelemetry instrumentation of the MongoDB client. */ +public final class MongoTracing { + + /** Returns a new {@link MongoTracing} configured with the given {@link OpenTelemetry}. */ + public static MongoTracing create(OpenTelemetry openTelemetry) { + return newBuilder(openTelemetry).build(); + } + + /** Returns a new {@link MongoTracingBuilder} configured with the given {@link OpenTelemetry}. */ + public static MongoTracingBuilder newBuilder(OpenTelemetry openTelemetry) { + return new MongoTracingBuilder(openTelemetry); + } + + private final MongoClientTracer tracer; + + MongoTracing(OpenTelemetry openTelemetry, int maxNormalizedQueryLength) { + this.tracer = new MongoClientTracer(openTelemetry, maxNormalizedQueryLength); + } + + /** + * Returns a new {@link CommandListener} that can be used with methods like {@link + * com.mongodb.MongoClientOptions.Builder#addCommandListener(CommandListener)}. + */ + public CommandListener newCommandListener() { + return new TracingCommandListener(tracer); + } +} diff --git a/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTracingBuilder.java b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTracingBuilder.java new file mode 100644 index 0000000000..f06ed9ecf9 --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/MongoTracingBuilder.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.mongo.v3_1; + +import io.opentelemetry.api.OpenTelemetry; + +/** A builder of {@link MongoTracing}. */ +public final class MongoTracingBuilder { + + // Visible for testing + static final int DEFAULT_MAX_NORMALIZED_QUERY_LENGTH = 32 * 1024; + + private final OpenTelemetry openTelemetry; + + private int maxNormalizedQueryLength = DEFAULT_MAX_NORMALIZED_QUERY_LENGTH; + + MongoTracingBuilder(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + /** + * Sets the max length of recorded queries after normalization. Defaults to {@value + * DEFAULT_MAX_NORMALIZED_QUERY_LENGTH}. + */ + public MongoTracingBuilder setMaxNormalizedQueryLength(int maxNormalizedQueryLength) { + this.maxNormalizedQueryLength = maxNormalizedQueryLength; + return this; + } + + /** Returns a new {@link MongoTracing} with the settings of this {@link MongoTracingBuilder}. */ + public MongoTracing build() { + return new MongoTracing(openTelemetry, maxNormalizedQueryLength); + } +} diff --git a/instrumentation/mongo/mongo-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/TracingCommandListener.java b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/TracingCommandListener.java similarity index 62% rename from instrumentation/mongo/mongo-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/TracingCommandListener.java rename to instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/TracingCommandListener.java index bbee1d3163..ed28ef3b72 100644 --- a/instrumentation/mongo/mongo-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/TracingCommandListener.java +++ b/instrumentation/mongo/mongo-3.1/library/src/main/java/io/opentelemetry/instrumentation/mongo/v3_1/TracingCommandListener.java @@ -3,9 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.mongo; - -import static io.opentelemetry.javaagent.instrumentation.mongo.MongoClientTracer.tracer; +package io.opentelemetry.instrumentation.mongo.v3_1; import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandListener; @@ -15,13 +13,19 @@ import io.opentelemetry.context.Context; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public class TracingCommandListener implements CommandListener { +final class TracingCommandListener implements CommandListener { - private final Map contextMap = new ConcurrentHashMap<>(); + private final MongoClientTracer tracer; + private final Map contextMap; + + TracingCommandListener(MongoClientTracer tracer) { + this.tracer = tracer; + contextMap = new ConcurrentHashMap<>(); + } @Override public void commandStarted(CommandStartedEvent event) { - Context context = tracer().startSpan(Context.current(), event, event.getCommand()); + Context context = tracer.startSpan(Context.current(), event, event.getCommand()); contextMap.put(event.getRequestId(), context); } @@ -29,7 +33,7 @@ public class TracingCommandListener implements CommandListener { public void commandSucceeded(CommandSucceededEvent event) { Context context = contextMap.remove(event.getRequestId()); if (context != null) { - tracer().end(context); + tracer.end(context); } } @@ -37,7 +41,7 @@ public class TracingCommandListener implements CommandListener { public void commandFailed(CommandFailedEvent event) { Context context = contextMap.remove(event.getRequestId()); if (context != null) { - tracer().endExceptionally(context, event.getThrowable()); + tracer.endExceptionally(context, event.getThrowable()); } } } diff --git a/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.groovy b/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.groovy new file mode 100644 index 0000000000..48a5c607eb --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTest.groovy @@ -0,0 +1,16 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.mongo.v3_1 + +import com.mongodb.MongoClientOptions +import io.opentelemetry.instrumentation.test.LibraryTestTrait + +class MongoClientTest extends AbstractMongo31ClientTest implements LibraryTestTrait { + @Override + void configureMongoClientOptions(MongoClientOptions.Builder options) { + options.addCommandListener(MongoTracing.create(openTelemetry).newCommandListener()) + } +} diff --git a/instrumentation/mongo/mongo-common/javaagent/src/test/groovy/MongoClientTracerTest.groovy b/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTracerTest.groovy similarity index 79% rename from instrumentation/mongo/mongo-common/javaagent/src/test/groovy/MongoClientTracerTest.groovy rename to instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTracerTest.groovy index 9947f9c422..8961b8cc08 100644 --- a/instrumentation/mongo/mongo-common/javaagent/src/test/groovy/MongoClientTracerTest.groovy +++ b/instrumentation/mongo/mongo-3.1/library/src/test/groovy/io/opentelemetry/instrumentation/mongo/v3_1/MongoClientTracerTest.groovy @@ -3,10 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ +package io.opentelemetry.instrumentation.mongo.v3_1 + +import static io.opentelemetry.instrumentation.mongo.v3_1.MongoTracingBuilder.DEFAULT_MAX_NORMALIZED_QUERY_LENGTH import static java.util.Arrays.asList import com.mongodb.event.CommandStartedEvent -import io.opentelemetry.javaagent.instrumentation.mongo.MongoClientTracer +import io.opentelemetry.api.OpenTelemetry import org.bson.BsonArray import org.bson.BsonDocument import org.bson.BsonInt32 @@ -16,7 +19,7 @@ import spock.lang.Specification class MongoClientTracerTest extends Specification { def 'should sanitize statements to json'() { setup: - def tracer = new MongoClientTracer() + def tracer = new MongoClientTracer(OpenTelemetry.noop(), DEFAULT_MAX_NORMALIZED_QUERY_LENGTH) expect: sanitizeStatementAcrossVersions(tracer, @@ -36,7 +39,7 @@ class MongoClientTracerTest extends Specification { def 'should only preserve string value if it is the value of the first top-level key'() { setup: - def tracer = new MongoClientTracer() + def tracer = new MongoClientTracer(OpenTelemetry.noop(), DEFAULT_MAX_NORMALIZED_QUERY_LENGTH) expect: sanitizeStatementAcrossVersions(tracer, @@ -48,7 +51,7 @@ class MongoClientTracerTest extends Specification { def 'should truncate simple command'() { setup: - def tracer = new MongoClientTracer(20) + def tracer = new MongoClientTracer(OpenTelemetry.noop(), 20) def normalized = sanitizeStatementAcrossVersions(tracer, new BsonDocument("cmd", new BsonString("c")) @@ -61,11 +64,11 @@ class MongoClientTracerTest extends Specification { def 'should truncate array'() { setup: - def tracer = new MongoClientTracer(27) + def tracer = new MongoClientTracer(OpenTelemetry.noop(), 27) def normalized = sanitizeStatementAcrossVersions(tracer, new BsonDocument("cmd", new BsonString("c")) - .append("f1", new BsonArray(Arrays.asList(new BsonString("c1"), new BsonString("c2")))) + .append("f1", new BsonArray(asList(new BsonString("c1"), new BsonString("c2")))) .append("f2", new BsonString("c3"))) expect: // this can vary because of different whitespace for different mongo versions @@ -74,7 +77,7 @@ class MongoClientTracerTest extends Specification { def 'test span name with no dbName'() { setup: - def tracer = new MongoClientTracer() + def tracer = new MongoClientTracer(OpenTelemetry.noop(), DEFAULT_MAX_NORMALIZED_QUERY_LENGTH) def event = new CommandStartedEvent( 0, null, null, command, new BsonDocument(command, new BsonInt32(1))) diff --git a/instrumentation/mongo/mongo-3.1/testing/mongo-3.1-testing.gradle b/instrumentation/mongo/mongo-3.1/testing/mongo-3.1-testing.gradle new file mode 100644 index 0000000000..5b1ad514cd --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/testing/mongo-3.1-testing.gradle @@ -0,0 +1,11 @@ +apply from: "$rootDir/gradle/java.gradle" + +dependencies { + api project(':instrumentation:mongo:mongo-testing') + + compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0' + + implementation deps.groovy + implementation deps.opentelemetryApi + implementation deps.spock +} diff --git a/instrumentation/mongo/mongo-3.1/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.groovy b/instrumentation/mongo/mongo-3.1/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.groovy new file mode 100644 index 0000000000..a15c632b1d --- /dev/null +++ b/instrumentation/mongo/mongo-3.1/testing/src/main/groovy/io/opentelemetry/instrumentation/mongo/v3_1/AbstractMongo31ClientTest.groovy @@ -0,0 +1,184 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.mongo.v3_1 + +import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace + +import com.mongodb.MongoClient +import com.mongodb.MongoClientOptions +import com.mongodb.MongoTimeoutException +import com.mongodb.ServerAddress +import com.mongodb.client.MongoCollection +import com.mongodb.client.MongoDatabase +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest +import io.opentelemetry.instrumentation.test.utils.PortUtils +import org.bson.BsonDocument +import org.bson.BsonString +import org.bson.Document +import spock.lang.Shared + +abstract class AbstractMongo31ClientTest extends AbstractMongoClientTest> { + + abstract void configureMongoClientOptions(MongoClientOptions.Builder options); + + @Shared + MongoClient client + + def setupSpec() throws Exception { + def options = MongoClientOptions.builder().description("some-description") + configureMongoClientOptions(options) + client = new MongoClient(new ServerAddress("localhost", port), options.build()) + } + + def cleanupSpec() throws Exception { + client?.close() + client = null + } + + @Override + void createCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName) + db.createCollection(collectionName) + } + + @Override + void createCollectionNoDescription(String dbName, String collectionName) { + def options = MongoClientOptions.builder() + configureMongoClientOptions(options) + MongoDatabase db = new MongoClient(new ServerAddress("localhost", port), options.build()).getDatabase(dbName) + db.createCollection(collectionName) + } + + @Override + void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) { + def clientOptions = client.mongoClientOptions + def newClientOptions = MongoClientOptions.builder(clientOptions).build() + MongoDatabase db = new MongoClient(new ServerAddress("localhost", port), newClientOptions).getDatabase(dbName) + db.createCollection(collectionName) + } + + @Override + void createCollectionCallingBuildTwice(String dbName, String collectionName) { + def options = MongoClientOptions.builder().description("some-description") + configureMongoClientOptions(options) + options.build() + MongoDatabase db = new MongoClient(new ServerAddress("localhost", port), options.build()).getDatabase(dbName) + db.createCollection(collectionName) + } + + @Override + int getCollection(String dbName, String collectionName) { + MongoDatabase db = client.getDatabase(dbName) + return db.getCollection(collectionName).count() + } + + @Override + MongoCollection setupInsert(String dbName, String collectionName) { + MongoCollection collection = runUnderTrace("setup") { + MongoDatabase db = client.getDatabase(dbName) + db.createCollection(collectionName) + return db.getCollection(collectionName) + } + ignoreTracesAndClear(1) + return collection + } + + @Override + int insert(MongoCollection collection) { + collection.insertOne(new Document("password", "SECRET")) + return collection.count() + } + + @Override + MongoCollection setupUpdate(String dbName, String collectionName) { + MongoCollection collection = runUnderTrace("setup") { + MongoDatabase db = client.getDatabase(dbName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "OLDPW")) + return coll + } + ignoreTracesAndClear(1) + return collection + } + + @Override + int update(MongoCollection collection) { + def result = collection.updateOne( + new BsonDocument("password", new BsonString("OLDPW")), + new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW")))) + collection.count() + return result.modifiedCount + } + + @Override + MongoCollection setupDelete(String dbName, String collectionName) { + MongoCollection collection = runUnderTrace("setup") { + MongoDatabase db = client.getDatabase(dbName) + db.createCollection(collectionName) + def coll = db.getCollection(collectionName) + coll.insertOne(new Document("password", "SECRET")) + return coll + } + ignoreTracesAndClear(1) + return collection + } + + @Override + int delete(MongoCollection collection) { + def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET"))) + collection.count() + return result.deletedCount + } + + @Override + MongoCollection setupGetMore(String dbName, String collectionName) { + MongoCollection collection = runUnderTrace("setup") { + MongoDatabase db = client.getDatabase(dbName) + def coll = db.getCollection(collectionName) + coll.insertMany([new Document("_id", 0), new Document("_id", 1), new Document("_id", 2)]) + return coll + } + ignoreTracesAndClear(1) + return collection + } + + @Override + void getMore(MongoCollection collection) { + collection.find().filter(new Document("_id", new Document('$gte', 0))) + .batchSize(2).into(new ArrayList()) + } + + @Override + void error(String dbName, String collectionName) { + MongoCollection collection = runUnderTrace("setup") { + MongoDatabase db = client.getDatabase(dbName) + db.createCollection(collectionName) + return db.getCollection(collectionName) + } + ignoreTracesAndClear(1) + collection.updateOne(new BsonDocument(), new BsonDocument()) + } + + def "test client failure"() { + setup: + def options = MongoClientOptions.builder().serverSelectionTimeout(10).build() + def client = new MongoClient(new ServerAddress("localhost", PortUtils.UNUSABLE_PORT), [], options) + + when: + MongoDatabase db = client.getDatabase(dbName) + db.createCollection(collectionName) + + then: + thrown(MongoTimeoutException) + // Unfortunately not caught by our instrumentation. + assertTraces(0) {} + + where: + dbName = "test_db" + collectionName = createCollectionName() + } +} diff --git a/instrumentation/mongo/mongo-3.7/javaagent/mongo-3.7-javaagent.gradle b/instrumentation/mongo/mongo-3.7/javaagent/mongo-3.7-javaagent.gradle index 7ac2eab4f1..ba81dad002 100644 --- a/instrumentation/mongo/mongo-3.7/javaagent/mongo-3.7-javaagent.gradle +++ b/instrumentation/mongo/mongo-3.7/javaagent/mongo-3.7-javaagent.gradle @@ -19,11 +19,10 @@ muzzle { } dependencies { - implementation(project(':instrumentation:mongo:mongo-common:javaagent')) + implementation(project(':instrumentation:mongo:mongo-3.1:library')) // a couple of test attribute verifications don't pass until 3.8.0 library group: 'org.mongodb', name: 'mongo-java-driver', version: '3.8.0' testImplementation project(':instrumentation:mongo:mongo-testing') - testImplementation group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '1.50.5' } diff --git a/instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoClientInstrumentationModule.java b/instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoClientInstrumentationModule.java index 171748e07e..fee0b9206b 100644 --- a/instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoClientInstrumentationModule.java +++ b/instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoClientInstrumentationModule.java @@ -19,7 +19,6 @@ import com.mongodb.MongoClientSettings; import com.mongodb.async.SingleResultCallback; import com.mongodb.event.CommandListener; import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; -import io.opentelemetry.javaagent.instrumentation.mongo.TracingCommandListener; import io.opentelemetry.javaagent.tooling.InstrumentationModule; import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import java.util.HashMap; @@ -74,11 +73,11 @@ public class MongoClientInstrumentationModule extends InstrumentationModule { @Advice.This MongoClientSettings.Builder builder, @Advice.FieldValue("commandListeners") List commandListeners) { for (CommandListener commandListener : commandListeners) { - if (commandListener instanceof TracingCommandListener) { + if (commandListener == MongoInstrumentationSingletons.LISTENER) { return; } } - builder.addCommandListener(new TracingCommandListener()); + builder.addCommandListener(MongoInstrumentationSingletons.LISTENER); } } diff --git a/instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoInstrumentationSingletons.java b/instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoInstrumentationSingletons.java new file mode 100644 index 0000000000..b01c3fe330 --- /dev/null +++ b/instrumentation/mongo/mongo-3.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v3_7/MongoInstrumentationSingletons.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v3_7; + +import com.mongodb.event.CommandListener; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.mongo.v3_1.MongoTracing; + +public final class MongoInstrumentationSingletons { + + public static final CommandListener LISTENER = + MongoTracing.create(GlobalOpenTelemetry.get()).newCommandListener(); + + private MongoInstrumentationSingletons() {} +} diff --git a/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy b/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy index 89779e68d5..b51bef1715 100644 --- a/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy +++ b/instrumentation/mongo/mongo-3.7/javaagent/src/test/groovy/MongoClientTest.groovy @@ -13,12 +13,14 @@ import com.mongodb.client.MongoClient import com.mongodb.client.MongoClients import com.mongodb.client.MongoCollection import com.mongodb.client.MongoDatabase +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest +import io.opentelemetry.instrumentation.test.AgentTestTrait import org.bson.BsonDocument import org.bson.BsonString import org.bson.Document import spock.lang.Shared -class MongoClientTest extends AbstractMongoClientTest> { +class MongoClientTest extends AbstractMongoClientTest> implements AgentTestTrait { @Shared MongoClient client @@ -64,6 +66,19 @@ class MongoClientTest extends AbstractMongoClientTest> db.createCollection(collectionName) } + @Override + void createCollectionCallingBuildTwice(String dbName, String collectionName) { + def clientSettings = MongoClientSettings.builder() + .applyToClusterSettings({ builder -> + builder.hosts(Arrays.asList( + new ServerAddress("localhost", port))) + .description("some-description") + }) + clientSettings.build() + MongoDatabase db = MongoClients.create(clientSettings.build()).getDatabase(dbName) + db.createCollection(collectionName) + } + @Override int getCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) diff --git a/instrumentation/mongo/mongo-4.0/javaagent/mongo-4.0-javaagent.gradle b/instrumentation/mongo/mongo-4.0/javaagent/mongo-4.0-javaagent.gradle index a45bf03ed0..7b8cf54bb7 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/mongo-4.0-javaagent.gradle +++ b/instrumentation/mongo/mongo-4.0/javaagent/mongo-4.0-javaagent.gradle @@ -10,7 +10,7 @@ muzzle { } dependencies { - implementation(project(':instrumentation:mongo:mongo-common:javaagent')) + implementation(project(':instrumentation:mongo:mongo-3.1:library')) library group: 'org.mongodb', name: 'mongodb-driver-core', version: '4.0.0' diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java index 9a5ae650d0..c57a88128a 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoClientInstrumentationModule.java @@ -19,7 +19,6 @@ import com.mongodb.MongoClientSettings; import com.mongodb.event.CommandListener; import com.mongodb.internal.async.SingleResultCallback; import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; -import io.opentelemetry.javaagent.instrumentation.mongo.TracingCommandListener; import io.opentelemetry.javaagent.tooling.InstrumentationModule; import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import java.util.HashMap; @@ -74,11 +73,11 @@ public class MongoClientInstrumentationModule extends InstrumentationModule { @Advice.This MongoClientSettings.Builder builder, @Advice.FieldValue("commandListeners") List commandListeners) { for (CommandListener commandListener : commandListeners) { - if (commandListener instanceof TracingCommandListener) { + if (commandListener == MongoInstrumentationSingletons.LISTENER) { return; } } - builder.addCommandListener(new TracingCommandListener()); + builder.addCommandListener(MongoInstrumentationSingletons.LISTENER); } } diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoInstrumentationSingletons.java b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoInstrumentationSingletons.java new file mode 100644 index 0000000000..6e59cb7a20 --- /dev/null +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongo/v4_0/MongoInstrumentationSingletons.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongo.v4_0; + +import com.mongodb.event.CommandListener; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.mongo.v3_1.MongoTracing; + +public final class MongoInstrumentationSingletons { + + public static final CommandListener LISTENER = + MongoTracing.create(GlobalOpenTelemetry.get()).newCommandListener(); + + private MongoInstrumentationSingletons() {} +} diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/Mongo4ReactiveClientTest.groovy b/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/Mongo4ReactiveClientTest.groovy index 7c09fc462b..abae4cc105 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/Mongo4ReactiveClientTest.groovy +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/Mongo4ReactiveClientTest.groovy @@ -5,12 +5,16 @@ import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace +import com.mongodb.MongoClientSettings +import com.mongodb.ServerAddress import com.mongodb.client.result.DeleteResult import com.mongodb.client.result.UpdateResult import com.mongodb.reactivestreams.client.MongoClient import com.mongodb.reactivestreams.client.MongoClients import com.mongodb.reactivestreams.client.MongoCollection import com.mongodb.reactivestreams.client.MongoDatabase +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest +import io.opentelemetry.instrumentation.test.AgentTestTrait import java.util.concurrent.CompletableFuture import java.util.concurrent.CountDownLatch import org.bson.BsonDocument @@ -21,7 +25,7 @@ import org.reactivestreams.Subscriber import org.reactivestreams.Subscription import spock.lang.Shared -class Mongo4ReactiveClientTest extends AbstractMongoClientTest> { +class Mongo4ReactiveClientTest extends AbstractMongoClientTest> implements AgentTestTrait { @Shared MongoClient client @@ -52,6 +56,18 @@ class Mongo4ReactiveClientTest extends AbstractMongoClientTest + builder.hosts(Arrays.asList( + new ServerAddress("localhost", port))) + }) + settings.build() + MongoDatabase db = MongoClients.create(settings.build()).getDatabase(dbName) + db.createCollection(collectionName).subscribe(toSubscriber {}) + } + @Override int getCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) diff --git a/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/MongoClientTest.groovy b/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/MongoClientTest.groovy index 76c9340bee..ab6e99b431 100644 --- a/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/MongoClientTest.groovy +++ b/instrumentation/mongo/mongo-4.0/javaagent/src/test/groovy/MongoClientTest.groovy @@ -5,17 +5,21 @@ import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace +import com.mongodb.MongoClientSettings +import com.mongodb.ServerAddress import com.mongodb.client.MongoClient import com.mongodb.client.MongoClients import com.mongodb.client.MongoCollection import com.mongodb.client.MongoDatabase +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest +import io.opentelemetry.instrumentation.test.AgentTestTrait import org.bson.BsonDocument import org.bson.BsonString import org.bson.Document import org.junit.AssumptionViolatedException import spock.lang.Shared -class MongoClientTest extends AbstractMongoClientTest> { +class MongoClientTest extends AbstractMongoClientTest> implements AgentTestTrait { @Shared MongoClient client @@ -46,6 +50,18 @@ class MongoClientTest extends AbstractMongoClientTest> throw new AssumptionViolatedException("not tested on 4.0") } + @Override + void createCollectionCallingBuildTwice(String dbName, String collectionName) { + def settings = MongoClientSettings.builder() + .applyToClusterSettings({ builder -> + builder.hosts(Arrays.asList( + new ServerAddress("localhost", port))) + }) + settings.build() + MongoDatabase db = MongoClients.create(settings.build()).getDatabase(dbName) + db.createCollection(collectionName) + } + @Override int getCollection(String dbName, String collectionName) { MongoDatabase db = client.getDatabase(dbName) diff --git a/instrumentation/mongo/mongo-async-3.3/javaagent/mongo-async-3.3-javaagent.gradle b/instrumentation/mongo/mongo-async-3.3/javaagent/mongo-async-3.3-javaagent.gradle index f74084e0c3..ff53279a71 100644 --- a/instrumentation/mongo/mongo-async-3.3/javaagent/mongo-async-3.3-javaagent.gradle +++ b/instrumentation/mongo/mongo-async-3.3/javaagent/mongo-async-3.3-javaagent.gradle @@ -11,12 +11,11 @@ muzzle { } dependencies { - implementation(project(':instrumentation:mongo:mongo-common:javaagent')) + implementation(project(':instrumentation:mongo:mongo-3.1:library')) library group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.3.0' testImplementation project(':instrumentation:mongo:mongo-testing') - testImplementation group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '1.50.5' testInstrumentation project(':instrumentation:mongo:mongo-3.7:javaagent') } diff --git a/instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoAsyncClientInstrumentationModule.java b/instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoAsyncClientInstrumentationModule.java index 61b9d2f0bf..0ecd431fa1 100644 --- a/instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoAsyncClientInstrumentationModule.java +++ b/instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoAsyncClientInstrumentationModule.java @@ -20,7 +20,6 @@ import com.mongodb.async.SingleResultCallback; import com.mongodb.async.client.MongoClientSettings; import com.mongodb.event.CommandListener; import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; -import io.opentelemetry.javaagent.instrumentation.mongo.TracingCommandListener; import io.opentelemetry.javaagent.tooling.InstrumentationModule; import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import java.util.HashMap; @@ -76,11 +75,11 @@ public class MongoAsyncClientInstrumentationModule extends InstrumentationModule @Advice.This MongoClientSettings.Builder builder, @Advice.FieldValue("commandListeners") List commandListeners) { for (CommandListener commandListener : commandListeners) { - if (commandListener instanceof TracingCommandListener) { + if (commandListener == MongoInstrumentationSingletons.LISTENER) { return; } } - builder.addCommandListener(new TracingCommandListener()); + builder.addCommandListener(MongoInstrumentationSingletons.LISTENER); } } diff --git a/instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoInstrumentationSingletons.java b/instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoInstrumentationSingletons.java new file mode 100644 index 0000000000..2c8c3cc384 --- /dev/null +++ b/instrumentation/mongo/mongo-async-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/mongoasync/v3_3/MongoInstrumentationSingletons.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.mongoasync.v3_3; + +import com.mongodb.event.CommandListener; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.mongo.v3_1.MongoTracing; + +public final class MongoInstrumentationSingletons { + + public static final CommandListener LISTENER = + MongoTracing.create(GlobalOpenTelemetry.get()).newCommandListener(); + + private MongoInstrumentationSingletons() {} +} diff --git a/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy b/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy index b92bf786a9..f00d21c65e 100644 --- a/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy +++ b/instrumentation/mongo/mongo-async-3.3/javaagent/src/test/groovy/MongoAsyncClientTest.groovy @@ -15,6 +15,8 @@ import com.mongodb.async.client.MongoDatabase import com.mongodb.client.result.DeleteResult import com.mongodb.client.result.UpdateResult import com.mongodb.connection.ClusterSettings +import io.opentelemetry.instrumentation.mongo.testing.AbstractMongoClientTest +import io.opentelemetry.instrumentation.test.AgentTestTrait import java.util.concurrent.CompletableFuture import java.util.concurrent.CountDownLatch import org.bson.BsonDocument @@ -23,7 +25,7 @@ import org.bson.Document import org.junit.AssumptionViolatedException import spock.lang.Shared -class MongoAsyncClientTest extends AbstractMongoClientTest> { +class MongoAsyncClientTest extends AbstractMongoClientTest> implements AgentTestTrait{ @Shared MongoClient client @@ -64,6 +66,19 @@ class MongoAsyncClientTest extends AbstractMongoClientTest extends AgentInstrumentationSpecification { +abstract class AbstractMongoClientTest extends InstrumentationSpecification { @Shared GenericContainer mongodb @@ -50,6 +52,8 @@ abstract class AbstractMongoClientTest extends AgentInstrumentationSpecificat // This test asserts that duplicate traces are not created in those cases. abstract void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) + abstract void createCollectionCallingBuildTwice(String dbName, String collectionName) + abstract int getCollection(String dbName, String collectionName) abstract T setupInsert(String dbName, String collectionName) @@ -120,6 +124,29 @@ abstract class AbstractMongoClientTest extends AgentInstrumentationSpecificat collectionName = createCollectionName() } + def "test create collection calling build twice"() { + when: + runUnderTrace("parent") { + createCollectionCallingBuildTwice(dbName, collectionName) + } + + then: + assertTraces(1) { + trace(0, 2) { + basicSpan(it, 0, "parent") + mongoSpan(it, 1, "create", collectionName, dbName, span(0)) { + assert it == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" || + it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}" + true + } + } + } + + where: + dbName = "test_db" + collectionName = createCollectionName() + } + def "test get collection"() { when: def count = runUnderTrace("parent") { diff --git a/settings.gradle b/settings.gradle index d1d1bb712b..8036dc0451 100644 --- a/settings.gradle +++ b/settings.gradle @@ -186,10 +186,11 @@ include ':instrumentation:logback:logback-1.0:library' include ':instrumentation:logback:logback-1.0:testing' include ':instrumentation:methods:javaagent' include ':instrumentation:mongo:mongo-3.1:javaagent' +include ':instrumentation:mongo:mongo-3.1:library' +include ':instrumentation:mongo:mongo-3.1:testing' include ':instrumentation:mongo:mongo-3.7:javaagent' include ':instrumentation:mongo:mongo-4.0:javaagent' include ':instrumentation:mongo:mongo-async-3.3:javaagent' -include ':instrumentation:mongo:mongo-common:javaagent' include ':instrumentation:mongo:mongo-testing' include ':instrumentation:netty:netty-3.8:javaagent' include ':instrumentation:netty:netty-4.0:javaagent'