Extract Mongo library instrumentation (#2789)

* Extract mongo library instrumentation

* Finish

* Drift

* Cleanup

* build twice

* Spot
This commit is contained in:
Anuraag Agrawal 2021-04-14 13:51:21 +09:00 committed by GitHub
parent e87564ef12
commit b416ece9c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 534 additions and 240 deletions

View File

@ -121,6 +121,7 @@ public abstract class DatabaseClientTracer<CONNECTION, STATEMENT, SANITIZEDSTATE
return null; return null;
} }
@Nullable
protected String dbConnectionString(CONNECTION connection) { protected String dbConnectionString(CONNECTION connection) {
return null; return null;
} }
@ -129,6 +130,7 @@ public abstract class DatabaseClientTracer<CONNECTION, STATEMENT, SANITIZEDSTATE
netPeerAttributes.setNetPeer(span, peerAddress(connection)); netPeerAttributes.setNetPeer(span, peerAddress(connection));
} }
@Nullable
protected abstract InetSocketAddress peerAddress(CONNECTION connection); protected abstract InetSocketAddress peerAddress(CONNECTION connection);
protected void onStatement( protected void onStatement(

View File

@ -10,10 +10,9 @@ muzzle {
} }
dependencies { dependencies {
implementation(project(':instrumentation:mongo:mongo-common:javaagent')) implementation(project(':instrumentation:mongo:mongo-3.1:library'))
library group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0' library group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0'
testImplementation project(':instrumentation:mongo:mongo-testing') testImplementation project(':instrumentation:mongo:mongo-3.1:testing')
testImplementation group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '1.50.5'
} }

View File

@ -17,7 +17,6 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import com.mongodb.MongoClientOptions; import com.mongodb.MongoClientOptions;
import com.mongodb.event.CommandListener; import com.mongodb.event.CommandListener;
import io.opentelemetry.javaagent.instrumentation.mongo.TracingCommandListener;
import io.opentelemetry.javaagent.tooling.InstrumentationModule; import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.List; import java.util.List;
@ -68,11 +67,11 @@ public class MongoClientInstrumentationModule extends InstrumentationModule {
@Advice.This MongoClientOptions.Builder builder, @Advice.This MongoClientOptions.Builder builder,
@Advice.FieldValue("commandListeners") List<CommandListener> commandListeners) { @Advice.FieldValue("commandListeners") List<CommandListener> commandListeners) {
for (CommandListener commandListener : commandListeners) { for (CommandListener commandListener : commandListeners) {
if (commandListener instanceof TracingCommandListener) { if (commandListener == MongoInstrumentationSingletons.LISTENER) {
return; return;
} }
} }
builder.addCommandListener(new TracingCommandListener()); builder.addCommandListener(MongoInstrumentationSingletons.LISTENER);
} }
} }
} }

View File

@ -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() {}
}

View File

@ -3,167 +3,12 @@
* SPDX-License-Identifier: Apache-2.0 * 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.MongoClientOptions
import com.mongodb.MongoTimeoutException import io.opentelemetry.instrumentation.mongo.v3_1.AbstractMongo31ClientTest
import com.mongodb.ServerAddress import io.opentelemetry.instrumentation.test.AgentTestTrait
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<MongoCollection<Document>> {
@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
}
class MongoClientTest extends AbstractMongo31ClientTest implements AgentTestTrait {
@Override @Override
void createCollection(String dbName, String collectionName) { void configureMongoClientOptions(MongoClientOptions.Builder options) {
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<Document> setupInsert(String dbName, String collectionName) {
MongoCollection<Document> collection = runUnderTrace("setup") {
MongoDatabase db = client.getDatabase(dbName)
db.createCollection(collectionName)
return db.getCollection(collectionName)
}
ignoreTracesAndClear(1)
return collection
}
@Override
int insert(MongoCollection<Document> collection) {
collection.insertOne(new Document("password", "SECRET"))
return collection.count()
}
@Override
MongoCollection<Document> setupUpdate(String dbName, String collectionName) {
MongoCollection<Document> 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<Document> 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<Document> setupDelete(String dbName, String collectionName) {
MongoCollection<Document> 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<Document> collection) {
def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET")))
collection.count()
return result.deletedCount
}
@Override
MongoCollection<Document> setupGetMore(String dbName, String collectionName) {
MongoCollection<Document> 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<Document> collection) {
collection.find().filter(new Document("_id", new Document('$gte', 0)))
.batchSize(2).into(new ArrayList())
}
@Override
void error(String dbName, String collectionName) {
MongoCollection<Document> 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()
} }
} }

View File

@ -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')
}

View File

@ -3,13 +3,14 @@
* SPDX-License-Identifier: Apache-2.0 * 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 static java.util.Arrays.asList;
import com.mongodb.ServerAddress; import com.mongodb.ServerAddress;
import com.mongodb.connection.ConnectionDescription; import com.mongodb.connection.ConnectionDescription;
import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandStartedEvent;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes; import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes;
@ -29,46 +30,49 @@ import org.bson.BsonDocument;
import org.bson.BsonValue; import org.bson.BsonValue;
import org.bson.json.JsonWriter; import org.bson.json.JsonWriter;
import org.bson.json.JsonWriterSettings; import org.bson.json.JsonWriterSettings;
import org.checkerframework.checker.nullness.qual.Nullable;
public class MongoClientTracer final class MongoClientTracer
extends DatabaseClientTracer<CommandStartedEvent, BsonDocument, String> { extends DatabaseClientTracer<CommandStartedEvent, BsonDocument, String> {
private static final MongoClientTracer TRACER = new MongoClientTracer();
private final int maxNormalizedQueryLength; private final int maxNormalizedQueryLength;
private final JsonWriterSettings jsonWriterSettings; @Nullable private final JsonWriterSettings jsonWriterSettings;
public MongoClientTracer() { MongoClientTracer(OpenTelemetry openTelemetry, int maxNormalizedQueryLength) {
this(32 * 1024); super(openTelemetry, NetPeerAttributes.INSTANCE);
}
public MongoClientTracer(int maxNormalizedQueryLength) {
super(NetPeerAttributes.INSTANCE);
this.maxNormalizedQueryLength = maxNormalizedQueryLength; this.maxNormalizedQueryLength = maxNormalizedQueryLength;
this.jsonWriterSettings = createJsonWriterSettings(maxNormalizedQueryLength); this.jsonWriterSettings = createJsonWriterSettings(maxNormalizedQueryLength);
} }
public static MongoClientTracer tracer() {
return TRACER;
}
@Override @Override
protected String getInstrumentationName() { protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.mongo-common"; return "io.opentelemetry.javaagent.mongo-common";
} }
@Override @Override
// TODO(anuraaga): Migrate off of StringWriter to avoid synchronization.
@SuppressWarnings("JdkObsolete")
protected String sanitizeStatement(BsonDocument command) { protected String sanitizeStatement(BsonDocument command) {
StringWriter stringWriter = new StringWriter(128); 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 // 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 // JsonWriterSettings.Builder.maxLength in the static initializer for JSON_WRITER_SETTINGS
return stringWriter StringBuffer buf = stringWriter.getBuffer();
.getBuffer() if (buf.length() <= maxNormalizedQueryLength) {
.substring(0, Math.min(maxNormalizedQueryLength, stringWriter.getBuffer().length())); return buf.toString();
}
return buf.substring(0, maxNormalizedQueryLength);
} }
@Override @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)); return conventionSpanName(dbName(event), event.getCommandName(), collectionName(event));
} }
@ -92,6 +96,7 @@ public class MongoClientTracer
} }
@Override @Override
@Nullable
protected String dbConnectionString(CommandStartedEvent event) { protected String dbConnectionString(CommandStartedEvent event) {
ConnectionDescription connectionDescription = event.getConnectionDescription(); ConnectionDescription connectionDescription = event.getConnectionDescription();
if (connectionDescription != null) { if (connectionDescription != null) {
@ -109,6 +114,7 @@ public class MongoClientTracer
} }
@Override @Override
@Nullable
protected InetSocketAddress peerAddress(CommandStartedEvent event) { protected InetSocketAddress peerAddress(CommandStartedEvent event) {
if (event.getConnectionDescription() != null if (event.getConnectionDescription() != null
&& event.getConnectionDescription().getServerAddress() != null) { && event.getConnectionDescription().getServerAddress() != null) {
@ -130,7 +136,7 @@ public class MongoClientTracer
return event.getCommandName(); return event.getCommandName();
} }
private static final Method IS_TRUNCATED_METHOD; @Nullable private static final Method IS_TRUNCATED_METHOD;
static { static {
IS_TRUNCATED_METHOD = IS_TRUNCATED_METHOD =
@ -140,6 +146,7 @@ public class MongoClientTracer
.orElse(null); .orElse(null);
} }
@Nullable
private JsonWriterSettings createJsonWriterSettings(int maxNormalizedQueryLength) { private JsonWriterSettings createJsonWriterSettings(int maxNormalizedQueryLength) {
JsonWriterSettings settings = null; JsonWriterSettings settings = null;
try { try {
@ -175,14 +182,17 @@ public class MongoClientTracer
builderClass.getMethod("build", (Class<?>[]) null).invoke(builder, (Object[]) null); builderClass.getMethod("build", (Class<?>[]) null).invoke(builder, (Object[]) null);
} }
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) { } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) {
// Ignore
} }
if (settings == null) { if (settings == null) {
try { 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); settings = JsonWriterSettings.class.getConstructor(Boolean.TYPE).newInstance(false);
} catch (InstantiationException } catch (InstantiationException
| IllegalAccessException | IllegalAccessException
| InvocationTargetException | InvocationTargetException
| NoSuchMethodException ignored) { | NoSuchMethodException ignored) {
// Ignore
} }
} }
@ -264,6 +274,7 @@ public class MongoClientTracer
"createIndexes", "createIndexes",
"listIndexes")); "listIndexes"));
@Nullable
private static String collectionName(CommandStartedEvent event) { private static String collectionName(CommandStartedEvent event) {
if (event.getCommandName().equals("getMore")) { if (event.getCommandName().equals("getMore")) {
if (event.getCommand().containsKey("collection")) { if (event.getCommand().containsKey("collection")) {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -3,9 +3,7 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package io.opentelemetry.javaagent.instrumentation.mongo; package io.opentelemetry.instrumentation.mongo.v3_1;
import static io.opentelemetry.javaagent.instrumentation.mongo.MongoClientTracer.tracer;
import com.mongodb.event.CommandFailedEvent; import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandListener; import com.mongodb.event.CommandListener;
@ -15,13 +13,19 @@ import io.opentelemetry.context.Context;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class TracingCommandListener implements CommandListener { final class TracingCommandListener implements CommandListener {
private final Map<Integer, Context> contextMap = new ConcurrentHashMap<>(); private final MongoClientTracer tracer;
private final Map<Integer, Context> contextMap;
TracingCommandListener(MongoClientTracer tracer) {
this.tracer = tracer;
contextMap = new ConcurrentHashMap<>();
}
@Override @Override
public void commandStarted(CommandStartedEvent event) { 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); contextMap.put(event.getRequestId(), context);
} }
@ -29,7 +33,7 @@ public class TracingCommandListener implements CommandListener {
public void commandSucceeded(CommandSucceededEvent event) { public void commandSucceeded(CommandSucceededEvent event) {
Context context = contextMap.remove(event.getRequestId()); Context context = contextMap.remove(event.getRequestId());
if (context != null) { if (context != null) {
tracer().end(context); tracer.end(context);
} }
} }
@ -37,7 +41,7 @@ public class TracingCommandListener implements CommandListener {
public void commandFailed(CommandFailedEvent event) { public void commandFailed(CommandFailedEvent event) {
Context context = contextMap.remove(event.getRequestId()); Context context = contextMap.remove(event.getRequestId());
if (context != null) { if (context != null) {
tracer().endExceptionally(context, event.getThrowable()); tracer.endExceptionally(context, event.getThrowable());
} }
} }
} }

View File

@ -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())
}
}

View File

@ -3,10 +3,13 @@
* SPDX-License-Identifier: Apache-2.0 * 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 static java.util.Arrays.asList
import com.mongodb.event.CommandStartedEvent import com.mongodb.event.CommandStartedEvent
import io.opentelemetry.javaagent.instrumentation.mongo.MongoClientTracer import io.opentelemetry.api.OpenTelemetry
import org.bson.BsonArray import org.bson.BsonArray
import org.bson.BsonDocument import org.bson.BsonDocument
import org.bson.BsonInt32 import org.bson.BsonInt32
@ -16,7 +19,7 @@ import spock.lang.Specification
class MongoClientTracerTest extends Specification { class MongoClientTracerTest extends Specification {
def 'should sanitize statements to json'() { def 'should sanitize statements to json'() {
setup: setup:
def tracer = new MongoClientTracer() def tracer = new MongoClientTracer(OpenTelemetry.noop(), DEFAULT_MAX_NORMALIZED_QUERY_LENGTH)
expect: expect:
sanitizeStatementAcrossVersions(tracer, 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'() { def 'should only preserve string value if it is the value of the first top-level key'() {
setup: setup:
def tracer = new MongoClientTracer() def tracer = new MongoClientTracer(OpenTelemetry.noop(), DEFAULT_MAX_NORMALIZED_QUERY_LENGTH)
expect: expect:
sanitizeStatementAcrossVersions(tracer, sanitizeStatementAcrossVersions(tracer,
@ -48,7 +51,7 @@ class MongoClientTracerTest extends Specification {
def 'should truncate simple command'() { def 'should truncate simple command'() {
setup: setup:
def tracer = new MongoClientTracer(20) def tracer = new MongoClientTracer(OpenTelemetry.noop(), 20)
def normalized = sanitizeStatementAcrossVersions(tracer, def normalized = sanitizeStatementAcrossVersions(tracer,
new BsonDocument("cmd", new BsonString("c")) new BsonDocument("cmd", new BsonString("c"))
@ -61,11 +64,11 @@ class MongoClientTracerTest extends Specification {
def 'should truncate array'() { def 'should truncate array'() {
setup: setup:
def tracer = new MongoClientTracer(27) def tracer = new MongoClientTracer(OpenTelemetry.noop(), 27)
def normalized = sanitizeStatementAcrossVersions(tracer, def normalized = sanitizeStatementAcrossVersions(tracer,
new BsonDocument("cmd", new BsonString("c")) 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"))) .append("f2", new BsonString("c3")))
expect: expect:
// this can vary because of different whitespace for different mongo versions // 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'() { def 'test span name with no dbName'() {
setup: setup:
def tracer = new MongoClientTracer() def tracer = new MongoClientTracer(OpenTelemetry.noop(), DEFAULT_MAX_NORMALIZED_QUERY_LENGTH)
def event = new CommandStartedEvent( def event = new CommandStartedEvent(
0, null, null, command, new BsonDocument(command, new BsonInt32(1))) 0, null, null, command, new BsonDocument(command, new BsonInt32(1)))

View File

@ -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
}

View File

@ -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<MongoCollection<Document>> {
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<Document> setupInsert(String dbName, String collectionName) {
MongoCollection<Document> collection = runUnderTrace("setup") {
MongoDatabase db = client.getDatabase(dbName)
db.createCollection(collectionName)
return db.getCollection(collectionName)
}
ignoreTracesAndClear(1)
return collection
}
@Override
int insert(MongoCollection<Document> collection) {
collection.insertOne(new Document("password", "SECRET"))
return collection.count()
}
@Override
MongoCollection<Document> setupUpdate(String dbName, String collectionName) {
MongoCollection<Document> 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<Document> 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<Document> setupDelete(String dbName, String collectionName) {
MongoCollection<Document> 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<Document> collection) {
def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET")))
collection.count()
return result.deletedCount
}
@Override
MongoCollection<Document> setupGetMore(String dbName, String collectionName) {
MongoCollection<Document> 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<Document> collection) {
collection.find().filter(new Document("_id", new Document('$gte', 0)))
.batchSize(2).into(new ArrayList())
}
@Override
void error(String dbName, String collectionName) {
MongoCollection<Document> 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()
}
}

View File

@ -19,11 +19,10 @@ muzzle {
} }
dependencies { 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 // 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' library group: 'org.mongodb', name: 'mongo-java-driver', version: '3.8.0'
testImplementation project(':instrumentation:mongo:mongo-testing') testImplementation project(':instrumentation:mongo:mongo-testing')
testImplementation group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '1.50.5'
} }

View File

@ -19,7 +19,6 @@ import com.mongodb.MongoClientSettings;
import com.mongodb.async.SingleResultCallback; import com.mongodb.async.SingleResultCallback;
import com.mongodb.event.CommandListener; import com.mongodb.event.CommandListener;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; 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.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.HashMap; import java.util.HashMap;
@ -74,11 +73,11 @@ public class MongoClientInstrumentationModule extends InstrumentationModule {
@Advice.This MongoClientSettings.Builder builder, @Advice.This MongoClientSettings.Builder builder,
@Advice.FieldValue("commandListeners") List<CommandListener> commandListeners) { @Advice.FieldValue("commandListeners") List<CommandListener> commandListeners) {
for (CommandListener commandListener : commandListeners) { for (CommandListener commandListener : commandListeners) {
if (commandListener instanceof TracingCommandListener) { if (commandListener == MongoInstrumentationSingletons.LISTENER) {
return; return;
} }
} }
builder.addCommandListener(new TracingCommandListener()); builder.addCommandListener(MongoInstrumentationSingletons.LISTENER);
} }
} }

View File

@ -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() {}
}

View File

@ -13,12 +13,14 @@ import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients import com.mongodb.client.MongoClients
import com.mongodb.client.MongoCollection import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase 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.BsonDocument
import org.bson.BsonString import org.bson.BsonString
import org.bson.Document import org.bson.Document
import spock.lang.Shared import spock.lang.Shared
class MongoClientTest extends AbstractMongoClientTest<MongoCollection<Document>> { class MongoClientTest extends AbstractMongoClientTest<MongoCollection<Document>> implements AgentTestTrait {
@Shared @Shared
MongoClient client MongoClient client
@ -64,6 +66,19 @@ class MongoClientTest extends AbstractMongoClientTest<MongoCollection<Document>>
db.createCollection(collectionName) 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 @Override
int getCollection(String dbName, String collectionName) { int getCollection(String dbName, String collectionName) {
MongoDatabase db = client.getDatabase(dbName) MongoDatabase db = client.getDatabase(dbName)

View File

@ -10,7 +10,7 @@ muzzle {
} }
dependencies { 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' library group: 'org.mongodb', name: 'mongodb-driver-core', version: '4.0.0'

View File

@ -19,7 +19,6 @@ import com.mongodb.MongoClientSettings;
import com.mongodb.event.CommandListener; import com.mongodb.event.CommandListener;
import com.mongodb.internal.async.SingleResultCallback; import com.mongodb.internal.async.SingleResultCallback;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; 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.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.HashMap; import java.util.HashMap;
@ -74,11 +73,11 @@ public class MongoClientInstrumentationModule extends InstrumentationModule {
@Advice.This MongoClientSettings.Builder builder, @Advice.This MongoClientSettings.Builder builder,
@Advice.FieldValue("commandListeners") List<CommandListener> commandListeners) { @Advice.FieldValue("commandListeners") List<CommandListener> commandListeners) {
for (CommandListener commandListener : commandListeners) { for (CommandListener commandListener : commandListeners) {
if (commandListener instanceof TracingCommandListener) { if (commandListener == MongoInstrumentationSingletons.LISTENER) {
return; return;
} }
} }
builder.addCommandListener(new TracingCommandListener()); builder.addCommandListener(MongoInstrumentationSingletons.LISTENER);
} }
} }

View File

@ -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() {}
}

View File

@ -5,12 +5,16 @@
import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace 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.DeleteResult
import com.mongodb.client.result.UpdateResult import com.mongodb.client.result.UpdateResult
import com.mongodb.reactivestreams.client.MongoClient import com.mongodb.reactivestreams.client.MongoClient
import com.mongodb.reactivestreams.client.MongoClients import com.mongodb.reactivestreams.client.MongoClients
import com.mongodb.reactivestreams.client.MongoCollection import com.mongodb.reactivestreams.client.MongoCollection
import com.mongodb.reactivestreams.client.MongoDatabase 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.CompletableFuture
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import org.bson.BsonDocument import org.bson.BsonDocument
@ -21,7 +25,7 @@ import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription import org.reactivestreams.Subscription
import spock.lang.Shared import spock.lang.Shared
class Mongo4ReactiveClientTest extends AbstractMongoClientTest<MongoCollection<Document>> { class Mongo4ReactiveClientTest extends AbstractMongoClientTest<MongoCollection<Document>> implements AgentTestTrait {
@Shared @Shared
MongoClient client MongoClient client
@ -52,6 +56,18 @@ class Mongo4ReactiveClientTest extends AbstractMongoClientTest<MongoCollection<D
throw new AssumptionViolatedException("not tested on 4.0") 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).subscribe(toSubscriber {})
}
@Override @Override
int getCollection(String dbName, String collectionName) { int getCollection(String dbName, String collectionName) {
MongoDatabase db = client.getDatabase(dbName) MongoDatabase db = client.getDatabase(dbName)

View File

@ -5,17 +5,21 @@
import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace 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.MongoClient
import com.mongodb.client.MongoClients import com.mongodb.client.MongoClients
import com.mongodb.client.MongoCollection import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase 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.BsonDocument
import org.bson.BsonString import org.bson.BsonString
import org.bson.Document import org.bson.Document
import org.junit.AssumptionViolatedException import org.junit.AssumptionViolatedException
import spock.lang.Shared import spock.lang.Shared
class MongoClientTest extends AbstractMongoClientTest<MongoCollection<Document>> { class MongoClientTest extends AbstractMongoClientTest<MongoCollection<Document>> implements AgentTestTrait {
@Shared @Shared
MongoClient client MongoClient client
@ -46,6 +50,18 @@ class MongoClientTest extends AbstractMongoClientTest<MongoCollection<Document>>
throw new AssumptionViolatedException("not tested on 4.0") 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 @Override
int getCollection(String dbName, String collectionName) { int getCollection(String dbName, String collectionName) {
MongoDatabase db = client.getDatabase(dbName) MongoDatabase db = client.getDatabase(dbName)

View File

@ -11,12 +11,11 @@ muzzle {
} }
dependencies { 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' library group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.3.0'
testImplementation project(':instrumentation:mongo:mongo-testing') 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') testInstrumentation project(':instrumentation:mongo:mongo-3.7:javaagent')
} }

View File

@ -20,7 +20,6 @@ import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoClientSettings; import com.mongodb.async.client.MongoClientSettings;
import com.mongodb.event.CommandListener; import com.mongodb.event.CommandListener;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge; 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.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.util.HashMap; import java.util.HashMap;
@ -76,11 +75,11 @@ public class MongoAsyncClientInstrumentationModule extends InstrumentationModule
@Advice.This MongoClientSettings.Builder builder, @Advice.This MongoClientSettings.Builder builder,
@Advice.FieldValue("commandListeners") List<CommandListener> commandListeners) { @Advice.FieldValue("commandListeners") List<CommandListener> commandListeners) {
for (CommandListener commandListener : commandListeners) { for (CommandListener commandListener : commandListeners) {
if (commandListener instanceof TracingCommandListener) { if (commandListener == MongoInstrumentationSingletons.LISTENER) {
return; return;
} }
} }
builder.addCommandListener(new TracingCommandListener()); builder.addCommandListener(MongoInstrumentationSingletons.LISTENER);
} }
} }

View File

@ -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() {}
}

View File

@ -15,6 +15,8 @@ import com.mongodb.async.client.MongoDatabase
import com.mongodb.client.result.DeleteResult import com.mongodb.client.result.DeleteResult
import com.mongodb.client.result.UpdateResult import com.mongodb.client.result.UpdateResult
import com.mongodb.connection.ClusterSettings 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.CompletableFuture
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import org.bson.BsonDocument import org.bson.BsonDocument
@ -23,7 +25,7 @@ import org.bson.Document
import org.junit.AssumptionViolatedException import org.junit.AssumptionViolatedException
import spock.lang.Shared import spock.lang.Shared
class MongoAsyncClientTest extends AbstractMongoClientTest<MongoCollection<Document>> { class MongoAsyncClientTest extends AbstractMongoClientTest<MongoCollection<Document>> implements AgentTestTrait{
@Shared @Shared
MongoClient client MongoClient client
@ -64,6 +66,19 @@ class MongoAsyncClientTest extends AbstractMongoClientTest<MongoCollection<Docum
db.createCollection(collectionName, toCallback {}) db.createCollection(collectionName, toCallback {})
} }
@Override
void createCollectionCallingBuildTwice(String dbName, String collectionName) {
def settings = MongoClientSettings.builder()
.clusterSettings(
ClusterSettings.builder()
.description("some-description")
.applyConnectionString(new ConnectionString("mongodb://localhost:$port"))
.build())
settings.build()
MongoDatabase db = MongoClients.create(settings.build()).getDatabase(dbName)
db.createCollection(collectionName, toCallback {})
}
@Override @Override
int getCollection(String dbName, String collectionName) { int getCollection(String dbName, String collectionName) {
MongoDatabase db = client.getDatabase(dbName) MongoDatabase db = client.getDatabase(dbName)

View File

@ -1,19 +0,0 @@
// not applying $rootDir/gradle/instrumentation.gradle because that brings running tests with agent
// infrastructure, and this module only wants to run unit tests
ext.mavenGroupId = 'io.opentelemetry.javaagent.instrumentation'
apply from: "$rootDir/gradle/java.gradle"
apply from: "$rootDir/gradle/publish.gradle"
archivesBaseName = projectDir.parentFile.name
dependencies {
compileOnly project(':instrumentation-api')
compileOnly project(':javaagent-api')
compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0'
testImplementation project(':instrumentation-api')
testImplementation project(':javaagent-api')
testImplementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0'
}

View File

@ -3,11 +3,13 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
package io.opentelemetry.instrumentation.mongo.testing
import static io.opentelemetry.api.trace.SpanKind.CLIENT import static io.opentelemetry.api.trace.SpanKind.CLIENT
import static io.opentelemetry.instrumentation.test.utils.TraceUtils.basicSpan import static io.opentelemetry.instrumentation.test.utils.TraceUtils.basicSpan
import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification import io.opentelemetry.instrumentation.test.InstrumentationSpecification
import io.opentelemetry.instrumentation.test.asserts.TraceAssert import io.opentelemetry.instrumentation.test.asserts.TraceAssert
import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.data.SpanData
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
@ -17,7 +19,7 @@ import org.testcontainers.containers.GenericContainer
import org.testcontainers.containers.output.Slf4jLogConsumer import org.testcontainers.containers.output.Slf4jLogConsumer
import spock.lang.Shared import spock.lang.Shared
abstract class AbstractMongoClientTest<T> extends AgentInstrumentationSpecification { abstract class AbstractMongoClientTest<T> extends InstrumentationSpecification {
@Shared @Shared
GenericContainer mongodb GenericContainer mongodb
@ -50,6 +52,8 @@ abstract class AbstractMongoClientTest<T> extends AgentInstrumentationSpecificat
// This test asserts that duplicate traces are not created in those cases. // This test asserts that duplicate traces are not created in those cases.
abstract void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName) abstract void createCollectionWithAlreadyBuiltClientOptions(String dbName, String collectionName)
abstract void createCollectionCallingBuildTwice(String dbName, String collectionName)
abstract int getCollection(String dbName, String collectionName) abstract int getCollection(String dbName, String collectionName)
abstract T setupInsert(String dbName, String collectionName) abstract T setupInsert(String dbName, String collectionName)
@ -120,6 +124,29 @@ abstract class AbstractMongoClientTest<T> extends AgentInstrumentationSpecificat
collectionName = createCollectionName() 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"() { def "test get collection"() {
when: when:
def count = runUnderTrace("parent") { def count = runUnderTrace("parent") {

View File

@ -186,10 +186,11 @@ include ':instrumentation:logback:logback-1.0:library'
include ':instrumentation:logback:logback-1.0:testing' include ':instrumentation:logback:logback-1.0:testing'
include ':instrumentation:methods:javaagent' include ':instrumentation:methods:javaagent'
include ':instrumentation:mongo:mongo-3.1: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-3.7:javaagent'
include ':instrumentation:mongo:mongo-4.0:javaagent' include ':instrumentation:mongo:mongo-4.0:javaagent'
include ':instrumentation:mongo:mongo-async-3.3:javaagent' include ':instrumentation:mongo:mongo-async-3.3:javaagent'
include ':instrumentation:mongo:mongo-common:javaagent'
include ':instrumentation:mongo:mongo-testing' include ':instrumentation:mongo:mongo-testing'
include ':instrumentation:netty:netty-3.8:javaagent' include ':instrumentation:netty:netty-3.8:javaagent'
include ':instrumentation:netty:netty-4.0:javaagent' include ':instrumentation:netty:netty-4.0:javaagent'