Merge branch 'master' of github.com:DataDog/dd-trace-java into labbati/config-file

This commit is contained in:
Luca Abbati 2019-06-17 08:11:50 -04:00
commit a3be0cbf9f
No known key found for this signature in database
GPG Key ID: 74DBB952D9BA17F2
55 changed files with 2148 additions and 656 deletions

View File

@ -4,6 +4,8 @@
# This folder will be saved by circleci and available after test runs.
set -e
#Enable '**' support
shopt -s globstar
REPORTS_DIR=./reports
mkdir -p $REPORTS_DIR >/dev/null 2>&1

View File

@ -4,6 +4,8 @@
# This folder will be saved by circleci and available after test runs.
set -e
#Enable '**' support
shopt -s globstar
TEST_RESULTS_DIR=./results
mkdir -p $TEST_RESULTS_DIR >/dev/null 2>&1

View File

@ -38,6 +38,7 @@ jobs:
- persist_to_workspace:
root: .
paths:
- .gradle
- workspace
- save_cache:
@ -93,7 +94,13 @@ jobs:
<<: *default_test_job
environment:
# We are building on Java8, this is our default JVM so no need to set more homes
- TEST_TASK: test latestDepTest jacocoTestReport jacocoTestCoverageVerification
- TEST_TASK: test jacocoTestReport jacocoTestCoverageVerification
test_latest8:
<<: *default_test_job
environment:
# We are building on Java8, this is our default JVM so no need to set more homes
- TEST_TASK: latestDepTest
test_ibm8:
<<: *default_test_job
@ -249,6 +256,12 @@ workflows:
filters:
tags:
only: /.*/
- test_latest8:
requires:
- build
filters:
tags:
only: /.*/
- test_ibm8:
requires:
- build
@ -305,6 +318,7 @@ workflows:
requires:
- test_7
- test_8
- test_latest8
- test_ibm8
- test_9
- test_10
@ -322,6 +336,7 @@ workflows:
requires:
- test_7
- test_8
- test_latest8
- test_ibm8
- test_9
- test_10

View File

@ -1,5 +1,7 @@
package datadog.trace.agent.decorator;
import datadog.trace.api.Config;
import datadog.trace.api.DDTags;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
@ -29,7 +31,11 @@ public abstract class DatabaseClientDecorator<CONNECTION> extends ClientDecorato
assert span != null;
if (connection != null) {
Tags.DB_USER.set(span, dbUser(connection));
Tags.DB_INSTANCE.set(span, dbInstance(connection));
final String instanceName = dbInstance(connection);
Tags.DB_INSTANCE.set(span, instanceName);
if (instanceName != null && Config.get().isDbClientSplitByInstance()) {
span.setTag(DDTags.SERVICE_NAME, instanceName);
}
}
return span;
}

View File

@ -260,7 +260,10 @@ public class ReferenceCreator extends ClassVisitor {
// * DONE field-source class (descriptor)
// * DONE field-source visibility from this point (PRIVATE?)
final Type ownerType = Type.getType("L" + owner + ";");
final Type ownerType =
owner.startsWith("[")
? underlyingType(Type.getType(owner))
: Type.getType("L" + owner + ";");
final Type fieldType = Type.getType(descriptor);
final List<Reference.Flag> fieldFlags = new ArrayList<>();
@ -334,7 +337,10 @@ public class ReferenceCreator extends ClassVisitor {
}
}
final Type ownerType = Type.getType("L" + owner + ";");
final Type ownerType =
owner.startsWith("[")
? underlyingType(Type.getType(owner))
: Type.getType("L" + owner + ";");
final List<Reference.Flag> methodFlags = new ArrayList<>();
methodFlags.add(

View File

@ -1,9 +1,12 @@
package datadog.trace.agent.decorator
import datadog.trace.api.Config
import datadog.trace.api.DDTags
import io.opentracing.Span
import io.opentracing.tag.Tags
import static datadog.trace.agent.test.utils.ConfigUtils.withConfigOverride
class DatabaseClientDecoratorTest extends ClientDecoratorTest {
def span = Mock(Span)
@ -35,21 +38,26 @@ class DatabaseClientDecoratorTest extends ClientDecoratorTest {
def decorator = newDecorator()
when:
decorator.onConnection(span, session)
withConfigOverride(Config.DB_CLIENT_HOST_SPLIT_BY_INSTANCE, "$renameService") {
decorator.onConnection(span, session)
}
then:
if (session) {
1 * span.setTag(Tags.DB_USER.key, session.user)
1 * span.setTag(Tags.DB_INSTANCE.key, session.instance)
if (renameService && session.instance) {
1 * span.setTag(DDTags.SERVICE_NAME, session.instance)
}
}
0 * _
where:
session | _
null | _
[user: "test-user"] | _
[instance: "test-instance"] | _
[user: "test-user", instance: "test-instance"] | _
renameService | session
false | null
true | [user: "test-user"]
false | [instance: "test-instance"]
true | [user: "test-user", instance: "test-instance"]
}
def "test onStatement"() {

View File

@ -26,12 +26,12 @@ class HttpClientDecoratorTest extends ClientDecoratorTest {
then:
if (req) {
1 * span.setTag(Tags.HTTP_METHOD.key, "test-method")
1 * span.setTag(Tags.HTTP_URL.key, "$testUrl")
1 * span.setTag(Tags.PEER_HOSTNAME.key, "test-host")
1 * span.setTag(Tags.PEER_PORT.key, 555)
1 * span.setTag(Tags.HTTP_METHOD.key, req.method)
1 * span.setTag(Tags.HTTP_URL.key, "$req.url")
1 * span.setTag(Tags.PEER_HOSTNAME.key, req.host)
1 * span.setTag(Tags.PEER_PORT.key, req.port)
if (renameService) {
1 * span.setTag(DDTags.SERVICE_NAME, "test-host")
1 * span.setTag(DDTags.SERVICE_NAME, req.host)
}
}
0 * _

View File

@ -1,12 +1,9 @@
package datadog.trace.agent.decorator
import datadog.trace.api.DDTags
import io.opentracing.Span
class OrmClientDecoratorTest extends DatabaseClientDecoratorTest {
def span = Mock(Span)
def "test onOperation #testName"() {
setup:
decorator = newDecorator({ e -> entityName })

View File

@ -97,13 +97,6 @@ dependencies {
testCompile deps.opentracingMock
testCompile deps.testLogging
testCompile deps.guava
testCompile group: 'org.assertj', name: 'assertj-core', version: '2.9.+'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.19.0'
testCompile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.4.2'
testCompile group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.4.2'
// run embedded mongodb for integration testing
testCompile group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '1.50.5'
}
tasks.withType(Test).configureEach {

View File

@ -23,8 +23,12 @@ class AkkaHttpClientInstrumentationTest extends HttpClientTest<AkkaHttpClientDec
.withMethod(HttpMethods.lookup(method).get())
.addHeaders(headers.collect { RawHeader.create(it.key, it.value) })
def response = Http.get(system).singleRequest(request, materializer).toCompletableFuture().get()
blockUntilChildSpansFinished(1)
def response
try {
response = Http.get(system).singleRequest(request, materializer).toCompletableFuture().get()
} finally {
blockUntilChildSpansFinished(1)
}
callback?.call()
return response.status().intValue()
}

View File

@ -7,9 +7,6 @@ import datadog.trace.agent.decorator.DatabaseClientDecorator;
import datadog.trace.api.DDSpanTypes;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
public class CassandraClientDecorator extends DatabaseClientDecorator<Session> {
public static final CassandraClientDecorator DECORATE = new CassandraClientDecorator();
@ -53,15 +50,7 @@ public class CassandraClientDecorator extends DatabaseClientDecorator<Session> {
if (result != null) {
final Host host = result.getExecutionInfo().getQueriedHost();
Tags.PEER_PORT.set(span, host.getSocketAddress().getPort());
Tags.PEER_HOSTNAME.set(span, host.getAddress().getHostName());
final InetAddress inetAddress = host.getSocketAddress().getAddress();
if (inetAddress instanceof Inet4Address) {
final byte[] address = inetAddress.getAddress();
Tags.PEER_HOST_IPV4.set(span, ByteBuffer.wrap(address).getInt());
} else {
Tags.PEER_HOST_IPV6.set(span, inetAddress.getHostAddress());
}
onPeerConnection(span, host.getSocketAddress().getAddress());
}
return span;
}

View File

@ -2,15 +2,21 @@ import com.datastax.driver.core.Cluster
import com.datastax.driver.core.Session
import datadog.opentracing.DDSpan
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.api.DDSpanTypes
import io.opentracing.tag.Tags
import org.cassandraunit.utils.EmbeddedCassandraServerHelper
import spock.lang.Shared
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
class CassandraClientTest extends AgentTestRunner {
@Shared
Cluster cluster
@Shared
int port = 9142
def setupSpec() {
/*
@ -34,72 +40,91 @@ class CassandraClientTest extends AgentTestRunner {
EmbeddedCassandraServerHelper.cleanEmbeddedCassandra()
}
def "sync traces"() {
def "test sync"() {
setup:
final Session session = cluster.newSession()
Session session = cluster.connect(keyspace)
session.execute("DROP KEYSPACE IF EXISTS sync_test")
session.execute(
"CREATE KEYSPACE sync_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}")
session.execute("CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )")
session.execute("INSERT INTO sync_test.users (id, name) values (uuid(), 'alice')")
session.execute("SELECT * FROM sync_test.users where name = 'alice' ALLOW FILTERING")
def query = "SELECT * FROM sync_test.users where name = 'alice' ALLOW FILTERING"
session.execute(statement)
expect:
session.getClass().getName().endsWith("cassandra.TracingSession")
TEST_WRITER.size() == 5
final DDSpan selectTrace = TEST_WRITER.get(TEST_WRITER.size() - 1).get(0)
assertTraces(keyspace ? 2 : 1) {
if (keyspace) {
trace(0, 1) {
cassandraSpan(it, 0, "USE $keyspace", null)
}
}
trace(keyspace ? 1 : 0, 1) {
cassandraSpan(it, 0, statement, keyspace)
}
}
selectTrace.getServiceName() == "cassandra"
selectTrace.getOperationName() == "cassandra.query"
selectTrace.getResourceName() == query
selectTrace.getSpanType() == DDSpanTypes.CASSANDRA
cleanup:
session.close()
selectTrace.getTags().get(Tags.COMPONENT.getKey()) == "java-cassandra"
selectTrace.getTags().get(Tags.DB_TYPE.getKey()) == "cassandra"
selectTrace.getTags().get(Tags.PEER_HOSTNAME.getKey()) == "localhost"
// More info about IPv4 tag: https://trello.com/c/2el2IwkF/174-mongodb-ot-contrib-provides-a-wrong-peeripv4
selectTrace.getTags().get(Tags.PEER_HOST_IPV4.getKey()) == 2130706433
selectTrace.getTags().get(Tags.PEER_PORT.getKey()) == 9142
selectTrace.getTags().get(Tags.SPAN_KIND.getKey()) == "client"
where:
statement | keyspace
"DROP KEYSPACE IF EXISTS sync_test" | null
"CREATE KEYSPACE sync_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | null
"CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )" | "sync_test"
"INSERT INTO sync_test.users (id, name) values (uuid(), 'alice')" | "sync_test"
"SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "sync_test"
}
def "async traces"() {
def "test async"() {
setup:
final Session session = cluster.connectAsync().get()
session.executeAsync("DROP KEYSPACE IF EXISTS async_test").get()
session
.executeAsync(
"CREATE KEYSPACE async_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}")
.get()
session.executeAsync("CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )").get()
session.executeAsync("INSERT INTO async_test.users (id, name) values (uuid(), 'alice')").get()
TEST_WRITER.waitForTraces(4)
session
.executeAsync("SELECT * FROM async_test.users where name = 'alice' ALLOW FILTERING")
.get()
TEST_WRITER.waitForTraces(5)
def query = "SELECT * FROM async_test.users where name = 'alice' ALLOW FILTERING"
Session session = cluster.connect(keyspace)
runUnderTrace("parent") {
session.executeAsync(statement)
blockUntilChildSpansFinished(1)
}
expect:
session.getClass().getName().endsWith("cassandra.TracingSession")
final DDSpan selectTrace = TEST_WRITER.get(TEST_WRITER.size() - 1).get(0)
assertTraces(keyspace ? 2 : 1) {
if (keyspace) {
trace(0, 1) {
cassandraSpan(it, 0, "USE $keyspace", null)
}
}
trace(keyspace ? 1 : 0, 2) {
basicSpan(it, 0, "parent")
cassandraSpan(it, 1, statement, keyspace, span(0))
}
}
selectTrace.getServiceName() == "cassandra"
selectTrace.getOperationName() == "cassandra.query"
selectTrace.getResourceName() == query
selectTrace.getSpanType() == DDSpanTypes.CASSANDRA
cleanup:
session.close()
selectTrace.getTags().get(Tags.COMPONENT.getKey()) == "java-cassandra"
selectTrace.getTags().get(Tags.DB_TYPE.getKey()) == "cassandra"
selectTrace.getTags().get(Tags.PEER_HOSTNAME.getKey()) == "localhost"
// More info about IPv4 tag: https://trello.com/c/2el2IwkF/174-mongodb-ot-contrib-provides-a-wrong-peeripv4
selectTrace.getTags().get(Tags.PEER_HOST_IPV4.getKey()) == 2130706433
selectTrace.getTags().get(Tags.PEER_PORT.getKey()) == 9142
selectTrace.getTags().get(Tags.SPAN_KIND.getKey()) == "client"
where:
statement | keyspace
"DROP KEYSPACE IF EXISTS async_test" | null
"CREATE KEYSPACE async_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | null
"CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )" | "async_test"
"INSERT INTO async_test.users (id, name) values (uuid(), 'alice')" | "async_test"
"SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "async_test"
}
def cassandraSpan(TraceAssert trace, int index, String statement, String keyspace, Object parentSpan = null, Throwable exception = null) {
trace.span(index) {
serviceName "cassandra"
operationName "cassandra.query"
resourceName statement
spanType DDSpanTypes.CASSANDRA
if (parentSpan == null) {
parent()
} else {
childOf((DDSpan) parentSpan)
}
tags {
"$Tags.COMPONENT.key" "java-cassandra"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.DB_INSTANCE.key" keyspace
"$Tags.DB_TYPE.key" "cassandra"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" port
defaultTags()
}
}
}
}

View File

@ -26,16 +26,12 @@ class SpringJpaTest extends AgentTestRunner {
!repo.findAll().iterator().hasNext()
assertTraces(1) {
trace(0, 2) {
trace(0, 1) {
span(0) {
serviceName "hsqldb"
resourceName "select customer0_.id as id1_0_, customer0_.firstName as firstNam2_0_, customer0_.lastName as lastName3_0_ from Customer customer0_"
spanType "sql"
}
span(1) {
serviceName "hsqldb"
spanType "sql"
childOf(span(0))
}
}
}
TEST_WRITER.clear()
@ -49,25 +45,22 @@ class SpringJpaTest extends AgentTestRunner {
// Behavior changed in new version:
def extraTrace = TEST_WRITER.size() == 2
assertTraces(extraTrace ? 2 : 1) {
trace(0, 2) {
span(0) {
serviceName "hsqldb"
spanType "sql"
}
span(1) {
serviceName "hsqldb"
spanType "sql"
childOf(span(0))
}
}
if (extraTrace) {
trace(1, 1) {
trace(0, 1) {
span(0) {
serviceName "hsqldb"
resourceName "call next value for hibernate_sequence"
spanType "sql"
}
}
}
trace(extraTrace ? 1 : 0, 1) {
span(0) {
serviceName "hsqldb"
resourceName ~/insert into Customer \(.*\) values \(.*, \?, \?\)/
spanType "sql"
}
}
}
TEST_WRITER.clear()
@ -78,20 +71,17 @@ class SpringJpaTest extends AgentTestRunner {
then:
customer.id == savedId
assertTraces(2) {
trace(0, 2) {
trace(0, 1) {
span(0) {
serviceName "hsqldb"
resourceName "select customer0_.id as id1_0_0_, customer0_.firstName as firstNam2_0_0_, customer0_.lastName as lastName3_0_0_ from Customer customer0_ where customer0_.id=?"
spanType "sql"
}
span(1) {
serviceName "hsqldb"
spanType "sql"
childOf(span(0))
}
}
trace(1, 1) {
span(0) {
serviceName "hsqldb"
resourceName "update Customer set firstName=?, lastName=? where id=?"
spanType "sql"
}
}
@ -105,16 +95,12 @@ class SpringJpaTest extends AgentTestRunner {
customer.id == savedId
customer.firstName == "Bill"
assertTraces(1) {
trace(0, 2) {
trace(0, 1) {
span(0) {
serviceName "hsqldb"
resourceName "select customer0_.id as id1_0_, customer0_.firstName as firstNam2_0_, customer0_.lastName as lastName3_0_ from Customer customer0_ where customer0_.lastName=?"
spanType "sql"
}
span(1) {
serviceName "hsqldb"
spanType "sql"
childOf(span(0))
}
}
}
TEST_WRITER.clear()
@ -124,20 +110,17 @@ class SpringJpaTest extends AgentTestRunner {
then:
assertTraces(2) {
trace(0, 2) {
trace(0, 1) {
span(0) {
serviceName "hsqldb"
resourceName "select customer0_.id as id1_0_0_, customer0_.firstName as firstNam2_0_0_, customer0_.lastName as lastName3_0_0_ from Customer customer0_ where customer0_.id=?"
spanType "sql"
}
span(1) {
serviceName "hsqldb"
spanType "sql"
childOf(span(0))
}
}
trace(1, 1) {
span(0) {
serviceName "hsqldb"
resourceName "delete from Customer where id=?"
spanType "sql"
}
}

View File

@ -42,7 +42,7 @@ class SlickTest extends AgentTestRunner {
"$Tags.DB_TYPE.key" SlickUtils.Driver()
"$Tags.DB_USER.key" SlickUtils.Username()
"db.instance" SlickUtils.Url()
"db.instance" SlickUtils.Db()
"span.origin.type" "org.h2.jdbc.JdbcPreparedStatement"
defaultTags()

View File

@ -35,8 +35,9 @@ class SlickUtils {
object SlickUtils {
val Driver = "h2"
val Db = "test"
val Username = "TESTUSER"
val Url = s"jdbc:${Driver}:mem:test"
val Url = s"jdbc:${Driver}:mem:${Db}"
val TestValue = 3
val TestQuery = "SELECT 3"

View File

@ -0,0 +1,18 @@
package datadog.trace.instrumentation.jdbc;
import lombok.Builder;
import lombok.Data;
@Data
@Builder(builderClassName = "Builder", toBuilder = true)
public class DBInfo {
public static DBInfo DEFAULT = new Builder().type("database").build();
private final String type;
private final String subtype;
private final String url;
private final String user;
private final String instance;
private final String db;
private final String host;
private final Integer port;
}

View File

@ -0,0 +1,71 @@
package datadog.trace.instrumentation.jdbc;
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@AutoService(Instrumenter.class)
public final class DriverInstrumentation extends Instrumenter.Default {
public DriverInstrumentation() {
super("jdbc");
}
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return not(isInterface()).and(safeHasSuperType(named("java.sql.Driver")));
}
@Override
public String[] helperClassNames() {
final List<String> helpers = new ArrayList<>(JDBCConnectionUrlParser.values().length + 4);
helpers.add(packageName + ".DBInfo");
helpers.add(packageName + ".DBInfo$Builder");
helpers.add(packageName + ".JDBCMaps");
helpers.add(packageName + ".JDBCConnectionUrlParser");
for (final JDBCConnectionUrlParser parser : JDBCConnectionUrlParser.values()) {
helpers.add(parser.getClass().getName());
}
return helpers.toArray(new String[0]);
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
return singletonMap(
nameStartsWith("connect")
.and(takesArgument(0, String.class))
.and(takesArgument(1, Properties.class))
.and(returns(Connection.class)),
DriverAdvice.class.getName());
}
public static class DriverAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void addDBInfo(
@Advice.Argument(0) final String url,
@Advice.Argument(1) final Properties props,
@Advice.Return final Connection connection) {
final DBInfo dbInfo = JDBCConnectionUrlParser.parse(url, props);
JDBCMaps.connectionInfo.put(connection, dbInfo);
}
}
}

View File

@ -0,0 +1,802 @@
package datadog.trace.instrumentation.jdbc;
import static datadog.trace.instrumentation.jdbc.DBInfo.DEFAULT;
import datadog.trace.bootstrap.ExceptionLogger;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Structured as an enum instead of a class hierarchy to allow iterating through the parsers
* automatically without having to maintain a separate list of parsers.
*/
public enum JDBCConnectionUrlParser {
GENERIC_URL_LIKE() {
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
try {
// Attempt generic parsing
final URI uri = new URI(jdbcUrl);
populateStandardProperties(builder, splitQuery(uri.getQuery(), "&"));
final String user = uri.getUserInfo();
if (user != null) {
builder.user(user);
}
String path = uri.getPath();
if (path.startsWith("/")) {
path = path.substring(1);
}
if (!path.isEmpty()) {
builder.db(path);
}
if (uri.getHost() != null) {
builder.host(uri.getHost());
}
if (uri.getPort() > 0) {
builder.port(uri.getPort());
}
return builder.type(uri.getScheme());
} catch (final Exception e) {
return builder;
}
}
},
MODIFIED_URL_LIKE() {
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final String type;
String serverName = "";
Integer port = null;
String instanceName = null;
final String user = null;
final int hostIndex = jdbcUrl.indexOf("://");
if (hostIndex <= 0) {
return builder;
}
type = jdbcUrl.substring(0, hostIndex);
final String[] split;
if (type.equals("db2") || type.equals("as400")) {
if (jdbcUrl.contains("=")) {
final int paramLoc = jdbcUrl.lastIndexOf(":");
split = new String[] {jdbcUrl.substring(0, paramLoc), jdbcUrl.substring(paramLoc + 1)};
} else {
split = new String[] {jdbcUrl};
}
} else {
split = jdbcUrl.split(";", 2);
}
if (split.length > 1) {
final Map<String, String> props = splitQuery(split[1], ";");
populateStandardProperties(builder, props);
if (props.containsKey("servername")) {
serverName = props.get("servername");
}
}
final String urlServerName = split[0].substring(hostIndex + 3);
if (!urlServerName.isEmpty()) {
serverName = urlServerName;
}
int instanceLoc = serverName.indexOf("/");
if (instanceLoc > 1) {
instanceName = serverName.substring(instanceLoc + 1);
serverName = serverName.substring(0, instanceLoc);
}
final int portLoc = serverName.indexOf(":");
if (portLoc > 1) {
port = Integer.parseInt(serverName.substring(portLoc + 1));
serverName = serverName.substring(0, portLoc);
}
instanceLoc = serverName.indexOf("\\");
if (instanceLoc > 1) {
instanceName = serverName.substring(instanceLoc + 1);
serverName = serverName.substring(0, instanceLoc);
}
if (instanceName != null) {
builder.instance(instanceName);
}
if (!serverName.isEmpty()) {
builder.host(serverName);
}
if (port != null) {
builder.port(port);
}
if (user != null) {
builder.user(user);
}
return builder.type(type);
}
},
POSTGRES("postgresql") {
private static final String DEFAULT_HOST = "localhost";
private static final int DEFAULT_PORT = 5432;
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getHost() == null) {
builder.host(DEFAULT_HOST);
}
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return GENERIC_URL_LIKE.doParse(jdbcUrl, builder);
}
},
MYSQL("mysql", "mariadb") {
private static final String DEFAULT_HOST = "localhost";
private static final int DEFAULT_PORT = 3306;
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getHost() == null) {
builder.host(DEFAULT_HOST);
}
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
final int protoLoc = jdbcUrl.indexOf("://");
final int typeEndLoc = dbInfo.getType().length();
if (protoLoc > typeEndLoc) {
return MARIA_SUBPROTO
.doParse(jdbcUrl.substring(protoLoc + 3), builder)
.subtype(jdbcUrl.substring(typeEndLoc + 1, protoLoc));
}
if (protoLoc > 0) {
return GENERIC_URL_LIKE.doParse(jdbcUrl, builder);
}
final int hostEndLoc;
final int portLoc = jdbcUrl.indexOf(":", typeEndLoc + 1);
final int dbLoc = jdbcUrl.indexOf("/", typeEndLoc);
final int paramLoc = jdbcUrl.indexOf("?", dbLoc);
if (paramLoc > 0) {
populateStandardProperties(builder, splitQuery(jdbcUrl.substring(paramLoc + 1), "&"));
builder.db(jdbcUrl.substring(dbLoc + 1, paramLoc));
} else {
builder.db(jdbcUrl.substring(dbLoc + 1));
}
if (portLoc > 0) {
hostEndLoc = portLoc;
try {
builder.port(Integer.parseInt(jdbcUrl.substring(portLoc + 1, dbLoc)));
} catch (final NumberFormatException e) {
}
} else {
hostEndLoc = dbLoc;
}
builder.host(jdbcUrl.substring(typeEndLoc + 1, hostEndLoc));
return builder;
}
},
MARIA_SUBPROTO() {
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final int hostEndLoc;
final int clusterSepLoc = jdbcUrl.indexOf(",");
final int ipv6End = jdbcUrl.startsWith("[") ? jdbcUrl.indexOf("]") : -1;
int portLoc = jdbcUrl.indexOf(":", Math.max(0, ipv6End));
portLoc = clusterSepLoc < portLoc ? -1 : portLoc;
final int dbLoc = jdbcUrl.indexOf("/", Math.max(portLoc, clusterSepLoc));
final int paramLoc = jdbcUrl.indexOf("?", dbLoc);
if (paramLoc > 0) {
populateStandardProperties(builder, splitQuery(jdbcUrl.substring(paramLoc + 1), "&"));
builder.db(jdbcUrl.substring(dbLoc + 1, paramLoc));
} else {
builder.db(jdbcUrl.substring(dbLoc + 1));
}
if (jdbcUrl.startsWith("address=")) {
return MARIA_ADDRESS.doParse(jdbcUrl, builder);
}
if (portLoc > 0) {
hostEndLoc = portLoc;
final int portEndLoc = clusterSepLoc > 0 ? clusterSepLoc : dbLoc;
try {
builder.port(Integer.parseInt(jdbcUrl.substring(portLoc + 1, portEndLoc)));
} catch (final NumberFormatException e) {
}
} else {
hostEndLoc = clusterSepLoc > 0 ? clusterSepLoc : dbLoc;
}
if (ipv6End > 0) {
builder.host(jdbcUrl.substring(1, ipv6End));
} else {
builder.host(jdbcUrl.substring(0, hostEndLoc));
}
return builder;
}
},
MARIA_ADDRESS() {
private final Pattern HOST_REGEX = Pattern.compile("\\(\\s*host\\s*=\\s*([^ )]+)\\s*\\)");
private final Pattern PORT_REGEX = Pattern.compile("\\(\\s*port\\s*=\\s*([\\d]+)\\s*\\)");
private final Pattern USER_REGEX = Pattern.compile("\\(\\s*user\\s*=\\s*([^ )]+)\\s*\\)");
@Override
DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder) {
final int addressEnd = jdbcUrl.indexOf(",address=");
if (addressEnd > 0) {
jdbcUrl = jdbcUrl.substring(0, addressEnd);
}
final Matcher hostMatcher = HOST_REGEX.matcher(jdbcUrl);
if (hostMatcher.find()) {
builder.host(hostMatcher.group(1));
}
final Matcher portMatcher = PORT_REGEX.matcher(jdbcUrl);
if (portMatcher.find()) {
builder.port(Integer.parseInt(portMatcher.group(1)));
}
final Matcher userMatcher = USER_REGEX.matcher(jdbcUrl);
if (userMatcher.find()) {
builder.user(userMatcher.group(1));
}
return builder;
}
},
SAP("sap") {
private static final String DEFAULT_HOST = "localhost";
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getHost() == null) {
builder.host(DEFAULT_HOST);
}
return GENERIC_URL_LIKE.doParse(jdbcUrl, builder);
}
},
MSSQLSERVER("microsoft", "sqlserver") {
private static final String DEFAULT_HOST = "localhost";
private static final int DEFAULT_PORT = 1433;
private static final String DEFAULT_INSTANCE = "MSSQLSERVER";
@Override
DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder) {
if (jdbcUrl.startsWith("microsoft:")) {
jdbcUrl = jdbcUrl.substring("microsoft:".length());
}
if (!jdbcUrl.startsWith("sqlserver://")) {
return builder;
}
builder.type("sqlserver");
final DBInfo dbInfo = builder.build();
if (dbInfo.getInstance() == null) {
builder.instance(DEFAULT_INSTANCE);
}
if (dbInfo.getHost() == null) {
builder.host(DEFAULT_HOST);
}
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder);
}
},
DB2("db2", "as400") {
private static final int DEFAULT_PORT = 50000;
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder);
}
},
ORACLE("oracle") {
private static final int DEFAULT_PORT = 1521;
@Override
DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder) {
final int typeEndIndex = jdbcUrl.indexOf(":", "oracle:".length());
final String subtype = jdbcUrl.substring("oracle:".length(), typeEndIndex);
jdbcUrl = jdbcUrl.substring(typeEndIndex + 1);
builder.subtype(subtype);
final DBInfo dbInfo = builder.build();
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
if (jdbcUrl.contains("@")) {
return ORACLE_AT.doParse(jdbcUrl, builder);
} else {
return ORACLE_CONNECT_INFO.doParse(jdbcUrl, builder);
}
}
},
ORACLE_CONNECT_INFO() {
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final String host;
final Integer port;
final String instance;
final int hostEnd = jdbcUrl.indexOf(":");
final int instanceLoc = jdbcUrl.indexOf("/");
if (hostEnd > 0) {
host = jdbcUrl.substring(0, hostEnd);
final int afterHostEnd = jdbcUrl.indexOf(":", hostEnd + 1);
if (afterHostEnd > 0) {
port = Integer.parseInt(jdbcUrl.substring(hostEnd + 1, afterHostEnd));
instance = jdbcUrl.substring(afterHostEnd + 1);
} else {
if (instanceLoc > 0) {
instance = jdbcUrl.substring(instanceLoc + 1);
port = Integer.parseInt(jdbcUrl.substring(hostEnd + 1, instanceLoc));
} else {
final String portOrInstance = jdbcUrl.substring(hostEnd + 1);
Integer parsedPort = null;
try {
parsedPort = Integer.parseInt(portOrInstance);
} catch (final NumberFormatException e) {
}
if (parsedPort == null) {
port = null;
instance = portOrInstance;
} else {
port = parsedPort;
instance = null;
}
}
}
} else {
if (instanceLoc > 0) {
host = jdbcUrl.substring(0, instanceLoc);
port = null;
instance = jdbcUrl.substring(instanceLoc + 1);
} else {
if (jdbcUrl.isEmpty()) {
return builder;
} else {
host = null;
port = null;
instance = jdbcUrl;
}
}
}
if (host != null) {
builder.host(host);
}
if (port != null) {
builder.port(port);
}
return builder.instance(instance);
}
},
ORACLE_AT() {
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
if (jdbcUrl.contains("@(description")) {
return ORACLE_AT_DESCRIPTION.doParse(jdbcUrl, builder);
}
final String user;
final String[] atSplit = jdbcUrl.split("@", 2);
final int userInfoLoc = atSplit[0].indexOf("/");
if (userInfoLoc > 0) {
user = atSplit[0].substring(0, userInfoLoc);
} else {
user = null;
}
final String connectInfo = atSplit[1];
final int hostStart;
if (connectInfo.startsWith("//")) {
hostStart = "//".length();
} else if (connectInfo.startsWith("ldap://")) {
hostStart = "ldap://".length();
} else {
hostStart = 0;
}
if (user != null) {
builder.user(user);
}
return ORACLE_CONNECT_INFO.doParse(connectInfo.substring(hostStart), builder);
}
},
/**
* This parser can locate incorrect data if multiple addresses are defined but not everything is
* defined in the first block. (It would locate data from subsequent address blocks.
*/
ORACLE_AT_DESCRIPTION() {
private final Pattern HOST_REGEX = Pattern.compile("\\(\\s*host\\s*=\\s*([^ )]+)\\s*\\)");
private final Pattern PORT_REGEX = Pattern.compile("\\(\\s*port\\s*=\\s*([\\d]+)\\s*\\)");
private final Pattern INSTANCE_REGEX =
Pattern.compile("\\(\\s*service_name\\s*=\\s*([^ )]+)\\s*\\)");
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final String[] atSplit = jdbcUrl.split("@", 2);
final int userInfoLoc = atSplit[0].indexOf("/");
if (userInfoLoc > 0) {
builder.user(atSplit[0].substring(0, userInfoLoc));
}
final Matcher hostMatcher = HOST_REGEX.matcher(atSplit[1]);
if (hostMatcher.find()) {
builder.host(hostMatcher.group(1));
}
final Matcher portMatcher = PORT_REGEX.matcher(atSplit[1]);
if (portMatcher.find()) {
builder.port(Integer.parseInt(portMatcher.group(1)));
}
final Matcher instanceMatcher = INSTANCE_REGEX.matcher(atSplit[1]);
if (instanceMatcher.find()) {
builder.instance(instanceMatcher.group(1));
}
return builder;
}
},
H2("h2") {
private static final int DEFAULT_PORT = 8082;
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final String instance;
final String h2Url = jdbcUrl.substring("h2:".length());
if (h2Url.startsWith("mem:")) {
builder.subtype("mem");
final int propLoc = h2Url.indexOf(";");
if (propLoc >= 0) {
instance = h2Url.substring("mem:".length(), propLoc);
} else {
instance = h2Url.substring("mem:".length());
}
} else if (h2Url.startsWith("file:")) {
builder.subtype("file");
final int propLoc = h2Url.indexOf(";");
if (propLoc >= 0) {
instance = h2Url.substring("file:".length(), propLoc);
} else {
instance = h2Url.substring("file:".length());
}
} else if (h2Url.startsWith("zip:")) {
builder.subtype("zip");
final int propLoc = h2Url.indexOf(";");
if (propLoc >= 0) {
instance = h2Url.substring("zip:".length(), propLoc);
} else {
instance = h2Url.substring("zip:".length());
}
} else if (h2Url.startsWith("tcp:")) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("h2").subtype("tcp");
} else if (h2Url.startsWith("ssl:")) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("h2").subtype("ssl");
} else {
builder.subtype("file");
final int propLoc = h2Url.indexOf(";");
if (propLoc >= 0) {
instance = h2Url.substring(0, propLoc);
} else {
instance = h2Url;
}
}
if (!instance.isEmpty()) {
builder.instance(instance);
}
return builder;
}
},
HSQL("hsqldb") {
private static final String DEFAULT_USER = "SA";
private static final int DEFAULT_PORT = 9001;
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
String instance = null;
final DBInfo dbInfo = builder.build();
if (dbInfo.getUser() == null) {
builder.user(DEFAULT_USER);
}
final String hsqlUrl = jdbcUrl.substring("hsqldb:".length());
if (hsqlUrl.startsWith("mem:")) {
builder.subtype("mem");
instance = hsqlUrl.substring("mem:".length());
} else if (hsqlUrl.startsWith("file:")) {
builder.subtype("file");
instance = hsqlUrl.substring("file:".length());
} else if (hsqlUrl.startsWith("res:")) {
builder.subtype("res");
instance = hsqlUrl.substring("res:".length());
} else if (hsqlUrl.startsWith("hsql:")) {
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("hsqldb").subtype("hsql");
} else if (hsqlUrl.startsWith("hsqls:")) {
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("hsqldb").subtype("hsqls");
} else if (hsqlUrl.startsWith("http:")) {
if (dbInfo.getPort() == null) {
builder.port(80);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("hsqldb").subtype("http");
} else if (hsqlUrl.startsWith("https:")) {
if (dbInfo.getPort() == null) {
builder.port(443);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("hsqldb").subtype("https");
} else {
builder.subtype("mem");
instance = hsqlUrl;
}
return builder.instance(instance);
}
},
DERBY("derby") {
private static final String DEFAULT_USER = "APP";
private static final int DEFAULT_PORT = 1527;
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
String instance = null;
String host = null;
final DBInfo dbInfo = builder.build();
if (dbInfo.getUser() == null) {
builder.user(DEFAULT_USER);
}
final String derbyUrl = jdbcUrl.substring("derby:".length());
final String[] split = derbyUrl.split(";", 2);
if (split.length > 1) {
populateStandardProperties(builder, splitQuery(split[1], ";"));
}
final String details = split[0];
if (details.startsWith("memory:")) {
builder.subtype("memory");
final String urlInstance = details.substring("memory:".length());
if (!urlInstance.isEmpty()) {
instance = urlInstance;
}
} else if (details.startsWith("directory:")) {
builder.subtype("directory");
final String urlInstance = details.substring("directory:".length());
if (!urlInstance.isEmpty()) {
instance = urlInstance;
}
} else if (details.startsWith("classpath:")) {
builder.subtype("classpath");
final String urlInstance = details.substring("classpath:".length());
if (!urlInstance.isEmpty()) {
instance = urlInstance;
}
} else if (details.startsWith("jar:")) {
builder.subtype("jar");
final String urlInstance = details.substring("jar:".length());
if (!urlInstance.isEmpty()) {
instance = urlInstance;
}
} else if (details.startsWith("//")) {
builder.subtype("network");
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
String url = details.substring("//".length());
final int instanceLoc = url.indexOf("/");
if (instanceLoc >= 0) {
instance = url.substring(instanceLoc + 1);
final int protoLoc = instance.indexOf(":");
if (protoLoc >= 0) {
instance = instance.substring(protoLoc + 1);
}
url = url.substring(0, instanceLoc);
}
final int portLoc = url.indexOf(":");
if (portLoc > 0) {
host = url.substring(0, portLoc);
builder.port(Integer.parseInt(url.substring(portLoc + 1)));
} else {
host = url;
}
} else {
builder.subtype("directory");
final String urlInstance = details;
if (!urlInstance.isEmpty()) {
instance = urlInstance;
}
}
if (host != null) {
builder.host(host);
}
return builder.instance(instance);
}
};
private static final Map<String, JDBCConnectionUrlParser> typeParsers = new HashMap<>();
static {
for (final JDBCConnectionUrlParser parser : JDBCConnectionUrlParser.values()) {
for (final String key : parser.typeKeys) {
typeParsers.put(key, parser);
}
}
}
private final String[] typeKeys;
JDBCConnectionUrlParser(final String... typeKeys) {
this.typeKeys = typeKeys;
}
abstract DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder);
public static DBInfo parse(String connectionUrl, final Properties props) {
if (connectionUrl == null) {
return DEFAULT;
}
// Make this easier and ignore case.
connectionUrl = connectionUrl.toLowerCase();
if (!connectionUrl.startsWith("jdbc:")) {
return DEFAULT;
}
final String jdbcUrl = connectionUrl.substring("jdbc:".length());
final int typeLoc = jdbcUrl.indexOf(':');
if (typeLoc < 1) {
// Invalid format: `jdbc:` or `jdbc::`
return DEFAULT;
}
final String baseType = jdbcUrl.substring(0, typeLoc);
final DBInfo.Builder parsedProps = DEFAULT.toBuilder().type(baseType);
populateStandardProperties(parsedProps, props);
try {
if (typeParsers.containsKey(baseType)) {
// Delegate to specific parser
return typeParsers.get(baseType).doParse(jdbcUrl, parsedProps).build();
}
return GENERIC_URL_LIKE.doParse(connectionUrl, parsedProps).build();
} catch (final Exception e) {
ExceptionLogger.LOGGER.debug("Error parsing URL", e);
return parsedProps.build();
}
}
// Source: https://stackoverflow.com/a/13592567
private static Map<String, String> splitQuery(final String query, final String separator) {
if (query == null || query.isEmpty()) {
return Collections.emptyMap();
}
final Map<String, String> query_pairs = new LinkedHashMap<>();
final String[] pairs = query.split(separator);
for (final String pair : pairs) {
try {
final int idx = pair.indexOf("=");
final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
if (!query_pairs.containsKey(key)) {
final String value =
idx > 0 && pair.length() > idx + 1
? URLDecoder.decode(pair.substring(idx + 1), "UTF-8")
: null;
query_pairs.put(key, value);
}
} catch (final UnsupportedEncodingException e) {
// Ignore.
}
}
return query_pairs;
}
private static void populateStandardProperties(
final DBInfo.Builder builder, final Map<? extends Object, ? extends Object> props) {
if (props != null && !props.isEmpty()) {
if (props.containsKey("user")) {
builder.user((String) props.get("user"));
}
if (props.containsKey("databasename")) {
builder.db((String) props.get("databasename"));
}
if (props.containsKey("databaseName")) {
builder.db((String) props.get("databaseName"));
}
if (props.containsKey("servername")) {
builder.host((String) props.get("servername"));
}
if (props.containsKey("serverName")) {
builder.host((String) props.get("serverName"));
}
if (props.containsKey("portnumber")) {
final String portNumber = (String) props.get("portnumber");
try {
builder.port(Integer.parseInt(portNumber));
} catch (final NumberFormatException e) {
ExceptionLogger.LOGGER.debug("Error parsing portnumber property: " + portNumber, e);
}
}
if (props.containsKey("portNumber")) {
final String portNumber = (String) props.get("portNumber");
try {
builder.port(Integer.parseInt(portNumber));
} catch (final NumberFormatException e) {
ExceptionLogger.LOGGER.debug("Error parsing portNumber property: " + portNumber, e);
}
}
}
}
}

View File

@ -10,9 +10,11 @@ import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
public class JDBCDecorator extends DatabaseClientDecorator<DBInfo> {
public static final JDBCDecorator DECORATE = new JDBCDecorator();
private static final String DB_QUERY = "DB Query";
@Override
protected String[] instrumentationNames() {
return new String[] {"jdbc"};
@ -39,22 +41,27 @@ public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
}
@Override
protected String dbUser(final JDBCMaps.DBInfo info) {
protected String dbUser(final DBInfo info) {
return info.getUser();
}
@Override
protected String dbInstance(final JDBCMaps.DBInfo info) {
return info.getUrl();
protected String dbInstance(final DBInfo info) {
if (info.getInstance() != null) {
return info.getInstance();
} else {
return info.getDb();
}
}
public Span onConnection(final Span span, final Connection connection) {
JDBCMaps.DBInfo dbInfo = JDBCMaps.connectionInfo.get(connection);
DBInfo dbInfo = JDBCMaps.connectionInfo.get(connection);
/**
* Logic to get the DBInfo from a JDBC Connection, if the connection was never seen before, the
* connectionInfo map will return null and will attempt to extract DBInfo from the connection.
* If the DBInfo can't be extracted, then the connection will be stored with the DEFAULT DBInfo
* as the value in the connectionInfo map to avoid retry overhead.
* Logic to get the DBInfo from a JDBC Connection, if the connection was not created via
* Driver.connect, or it has never seen before, the connectionInfo map will return null and will
* attempt to extract DBInfo from the connection. If the DBInfo can't be extracted, then the
* connection will be stored with the DEFAULT DBInfo as the value in the connectionInfo map to
* avoid retry overhead.
*/
{
if (dbInfo == null) {
@ -62,19 +69,12 @@ public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
final DatabaseMetaData metaData = connection.getMetaData();
final String url = metaData.getURL();
if (url != null) {
// Remove end of url to prevent passwords from leaking:
final String sanitizedURL = url.replaceAll("[?;].*", "");
final String type = url.split(":", -1)[1];
String user = metaData.getUserName();
if (user != null && user.trim().equals("")) {
user = null;
}
dbInfo = new JDBCMaps.DBInfo(sanitizedURL, type, user);
dbInfo = JDBCConnectionUrlParser.parse(url, connection.getClientInfo());
} else {
dbInfo = JDBCMaps.DBInfo.DEFAULT;
dbInfo = DBInfo.DEFAULT;
}
} catch (final SQLException se) {
dbInfo = JDBCMaps.DBInfo.DEFAULT;
dbInfo = DBInfo.DEFAULT;
}
JDBCMaps.connectionInfo.put(connection, dbInfo);
}
@ -89,7 +89,7 @@ public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
@Override
public Span onStatement(final Span span, final String statement) {
final String resourceName = statement == null ? JDBCMaps.DB_QUERY : statement;
final String resourceName = statement == null ? DB_QUERY : statement;
span.setTag(DDTags.RESOURCE_NAME, resourceName);
Tags.COMPONENT.set(span, "java-jdbc-statement");
return super.onStatement(span, statement);
@ -97,7 +97,7 @@ public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
public Span onPreparedStatement(final Span span, final PreparedStatement statement) {
final String sql = JDBCMaps.preparedStatements.get(statement);
final String resourceName = sql == null ? JDBCMaps.DB_QUERY : sql;
final String resourceName = sql == null ? DB_QUERY : sql;
span.setTag(DDTags.RESOURCE_NAME, resourceName);
Tags.COMPONENT.set(span, "java-jdbc-prepared_statement");
return super.onStatement(span, sql);

View File

@ -5,7 +5,6 @@ import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
import datadog.trace.bootstrap.WeakMap;
import java.sql.Connection;
import java.sql.PreparedStatement;
import lombok.Data;
/**
* JDBC instrumentation shares a global map of connection info.
@ -15,14 +14,4 @@ import lombok.Data;
public class JDBCMaps {
public static final WeakMap<Connection, DBInfo> connectionInfo = newWeakMap();
public static final WeakMap<PreparedStatement, String> preparedStatements = newWeakMap();
public static final String DB_QUERY = "DB Query";
@Data
public static class DBInfo {
public static DBInfo DEFAULT = new DBInfo("null", "database", null);
private final String url;
private final String type;
private final String user;
}
}

View File

@ -1,10 +1,12 @@
package datadog.trace.instrumentation.jdbc;
import datadog.trace.bootstrap.ExceptionLogger;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.Statement;
public abstract class JDBCUtils {
private static Field c3poField = null;
/**
* @param statement
@ -14,6 +16,13 @@ public abstract class JDBCUtils {
Connection connection;
try {
connection = statement.getConnection();
if (c3poField != null) {
if (connection.getClass().getName().equals("com.mchange.v2.c3p0.impl.NewProxyConnection")) {
return (Connection) c3poField.get(connection);
}
}
try {
// unwrap the connection to cache the underlying actual connection and to not cache proxy
// objects
@ -21,6 +30,15 @@ public abstract class JDBCUtils {
connection = connection.unwrap(Connection.class);
}
} catch (final Exception | AbstractMethodError e) {
// Attempt to work around c3po delegating to an connection that doesn't support unwrapping.
final Class<? extends Connection> connectionClass = connection.getClass();
if (connectionClass.getName().equals("com.mchange.v2.c3p0.impl.NewProxyConnection")) {
final Field inner = connectionClass.getDeclaredField("inner");
inner.setAccessible(true);
c3poField = inner;
return (Connection) c3poField.get(connection);
}
// perhaps wrapping isn't supported?
// ex: org.h2.jdbc.JdbcConnection v1.3.175
// or: jdts.jdbc which always throws `AbstractMethodError` (at least up to version 1.3)

View File

@ -20,6 +20,8 @@ import io.opentracing.noop.NoopScopeManager;
import io.opentracing.util.GlobalTracer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
@ -40,15 +42,23 @@ public final class PreparedStatementInstrumentation extends Instrumenter.Default
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ClientDecorator",
"datadog.trace.agent.decorator.DatabaseClientDecorator",
packageName + ".JDBCDecorator",
packageName + ".JDBCMaps",
packageName + ".JDBCMaps$DBInfo",
packageName + ".JDBCUtils",
};
final List<String> helpers = new ArrayList<>(JDBCConnectionUrlParser.values().length + 9);
helpers.add(packageName + ".DBInfo");
helpers.add(packageName + ".DBInfo$Builder");
helpers.add(packageName + ".JDBCUtils");
helpers.add(packageName + ".JDBCMaps");
helpers.add(packageName + ".JDBCConnectionUrlParser");
helpers.add("datadog.trace.agent.decorator.BaseDecorator");
helpers.add("datadog.trace.agent.decorator.ClientDecorator");
helpers.add("datadog.trace.agent.decorator.DatabaseClientDecorator");
helpers.add(packageName + ".JDBCDecorator");
for (final JDBCConnectionUrlParser parser : JDBCConnectionUrlParser.values()) {
helpers.add(parser.getClass().getName());
}
return helpers.toArray(new String[0]);
}
@Override

View File

@ -20,6 +20,8 @@ import io.opentracing.noop.NoopScopeManager;
import io.opentracing.util.GlobalTracer;
import java.sql.Connection;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
@ -40,15 +42,23 @@ public final class StatementInstrumentation extends Instrumenter.Default {
@Override
public String[] helperClassNames() {
return new String[] {
"datadog.trace.agent.decorator.BaseDecorator",
"datadog.trace.agent.decorator.ClientDecorator",
"datadog.trace.agent.decorator.DatabaseClientDecorator",
packageName + ".JDBCDecorator",
packageName + ".JDBCMaps",
packageName + ".JDBCMaps$DBInfo",
packageName + ".JDBCUtils",
};
final List<String> helpers = new ArrayList<>(JDBCConnectionUrlParser.values().length + 9);
helpers.add(packageName + ".DBInfo");
helpers.add(packageName + ".DBInfo$Builder");
helpers.add(packageName + ".JDBCUtils");
helpers.add(packageName + ".JDBCMaps");
helpers.add(packageName + ".JDBCConnectionUrlParser");
helpers.add("datadog.trace.agent.decorator.BaseDecorator");
helpers.add("datadog.trace.agent.decorator.ClientDecorator");
helpers.add("datadog.trace.agent.decorator.DatabaseClientDecorator");
helpers.add(packageName + ".JDBCDecorator");
for (final JDBCConnectionUrlParser parser : JDBCConnectionUrlParser.values()) {
helpers.add(parser.getClass().getName());
}
return helpers.toArray(new String[0]);
}
@Override

View File

@ -0,0 +1,181 @@
import datadog.trace.instrumentation.jdbc.DBInfo
import spock.lang.Shared
import spock.lang.Specification
import static datadog.trace.instrumentation.jdbc.JDBCConnectionUrlParser.parse
class JDBCConnectionUrlParserTest extends Specification {
@Shared
def stdProps = {
def prop = new Properties()
// https://download.oracle.com/otn-pub/jcp/jdbc-4_1-mrel-spec/jdbc4.1-fr-spec.pdf
prop.setProperty("databaseName", "stdDatabaseName")
prop.setProperty("dataSourceName", "stdDatasourceName")
prop.setProperty("description", "Some description")
prop.setProperty("networkProtocol", "stdProto")
prop.setProperty("password", "PASSWORD!")
prop.setProperty("portNumber", "9999")
prop.setProperty("roleName", "stdRoleName")
prop.setProperty("serverName", "stdServerName")
prop.setProperty("user", "stdUserName")
return prop
}()
def "invalid url returns default"() {
expect:
parse(url, null) == DBInfo.DEFAULT
where:
url | _
null | _
"" | _
"jdbc:" | _
"jdbc::" | _
"bogus:string" | _
}
def "verify #type:#subtype parsing of #url"() {
setup:
def info = parse(url, props)
expect:
info.url == expected.url
info.type == expected.type
info.host == expected.host
info.port == expected.port
info.user == expected.user
info.instance == expected.instance
info == expected
where:
url | props | type | subtype | user | host | port | instance | db
// https://jdbc.postgresql.org/documentation/94/connect.html
"jdbc:postgresql:///" | null | "postgresql" | null | null | "localhost" | 5432 | null | null
"jdbc:postgresql:///" | stdProps | "postgresql" | null | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
"jdbc:postgresql://pg.host" | null | "postgresql" | null | null | "pg.host" | 5432 | null | null
"jdbc:postgresql://pg.host:11/pgdb?user=pguser&password=PW" | null | "postgresql" | null | "pguser" | "pg.host" | 11 | null | "pgdb"
"jdbc:postgresql://pg.host:11/pgdb?user=pguser&password=PW" | stdProps | "postgresql" | null | "pguser" | "pg.host" | 11 | null | "pgdb"
// https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html
// https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html
"jdbc:mysql:///" | null | "mysql" | null | null | "localhost" | 3306 | null | null
"jdbc:mysql:///" | stdProps | "mysql" | null | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
"jdbc:mysql://my.host" | null | "mysql" | null | null | "my.host" | 3306 | null | null
"jdbc:mysql://my.host?user=myuser&password=PW" | null | "mysql" | null | "myuser" | "my.host" | 3306 | null | null
"jdbc:mysql://my.host:22/mydb?user=myuser&password=PW" | null | "mysql" | null | "myuser" | "my.host" | 22 | null | "mydb"
"jdbc:mysql://127.0.0.1:22/mydb?user=myuser&password=PW" | stdProps | "mysql" | null | "myuser" | "127.0.0.1" | 22 | null | "mydb"
// https://mariadb.com/kb/en/library/about-mariadb-connector-j/#connection-strings
"jdbc:mariadb:127.0.0.1:33/mdbdb" | null | "mariadb" | null | null | "127.0.0.1" | 33 | null | "mdbdb"
"jdbc:mariadb:localhost/mdbdb" | null | "mariadb" | null | null | "localhost" | 3306 | null | "mdbdb"
"jdbc:mariadb:localhost/mdbdb?user=mdbuser&password=PW" | stdProps | "mariadb" | null | "mdbuser" | "localhost" | 9999 | null | "mdbdb"
"jdbc:mariadb:localhost:33/mdbdb" | stdProps | "mariadb" | null | "stdUserName" | "localhost" | 33 | null | "mdbdb"
"jdbc:mariadb://mdb.host:33/mdbdb?user=mdbuser&password=PW" | null | "mariadb" | null | "mdbuser" | "mdb.host" | 33 | null | "mdbdb"
"jdbc:mariadb:aurora://mdb.host/mdbdb" | null | "mariadb" | "aurora" | null | "mdb.host" | 3306 | null | "mdbdb"
"jdbc:mysql:aurora://mdb.host/mdbdb" | null | "mysql" | "aurora" | null | "mdb.host" | 3306 | null | "mdbdb"
"jdbc:mysql:failover://localhost/mdbdb?autoReconnect=true" | null | "mysql" | "failover" | null | "localhost" | 3306 | null | "mdbdb"
"jdbc:mariadb:failover://mdb.host1:33,mdb.host/mdbdb?characterEncoding=utf8" | null | "mariadb" | "failover" | null | "mdb.host1" | 33 | null | "mdbdb"
"jdbc:mariadb:sequential://mdb.host1,mdb.host2:33/mdbdb" | null | "mariadb" | "sequential" | null | "mdb.host1" | 3306 | null | "mdbdb"
"jdbc:mariadb:loadbalance://127.0.0.1:33,mdb.host/mdbdb" | null | "mariadb" | "loadbalance" | null | "127.0.0.1" | 33 | null | "mdbdb"
"jdbc:mariadb:loadbalance://[2001:0660:7401:0200:0000:0000:0edf:bdd7]:33,mdb.host/mdbdb" | null | "mariadb" | "loadbalance" | null | "2001:0660:7401:0200:0000:0000:0edf:bdd7" | 33 | null | "mdbdb"
"jdbc:mysql:loadbalance://127.0.0.1,127.0.0.1:3306/mdbdb?user=mdbuser&password=PW" | null | "mysql" | "loadbalance" | "mdbuser" | "127.0.0.1" | 3306 | null | "mdbdb"
"jdbc:mariadb:replication://localhost:33,anotherhost:3306/mdbdb" | null | "mariadb" | "replication" | null | "localhost" | 33 | null | "mdbdb"
"jdbc:mysql:replication://address=(HOST=127.0.0.1)(port=33)(user=mdbuser)(password=PW)," +
"address=(host=mdb.host)(port=3306)(user=otheruser)(password=PW)/mdbdb?user=wrong&password=PW" | null | "mysql" | "replication" | "mdbuser" | "127.0.0.1" | 33 | null | "mdbdb"
"jdbc:mysql:replication://address=(HOST=mdb.host)," +
"address=(host=anotherhost)(port=3306)(user=wrong)(password=PW)/mdbdb?user=mdbuser&password=PW" | null | "mysql" | "replication" | "mdbuser" | "mdb.host" | 3306 | null | "mdbdb"
//https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url
"jdbc:microsoft:sqlserver://;" | null | "sqlserver" | null | null | "localhost" | 1433 | "MSSQLSERVER" | null
"jdbc:microsoft:sqlserver://;" | stdProps | "sqlserver" | null | "stdUserName" | "stdServerName" | 9999 | "MSSQLSERVER" | "stdDatabaseName"
"jdbc:sqlserver://ss.host\\ssinstance:44;databaseName=ssdb;user=ssuser;password=pw" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | "ssinstance" | "ssdb"
"jdbc:sqlserver://;serverName=ss.host\\ssinstance:44;DatabaseName=;" | null | "sqlserver" | null | null | "ss.host" | 44 | "ssinstance" | null
"jdbc:sqlserver://ss.host;serverName=althost;DatabaseName=ssdb;" | null | "sqlserver" | null | null | "ss.host" | 1433 | "MSSQLSERVER" | "ssdb"
"jdbc:microsoft:sqlserver://ss.host:44;DatabaseName=ssdb;user=ssuser;password=pw;user=ssuser2;" | null | "sqlserver" | null | "ssuser" | "ss.host" | 44 | "MSSQLSERVER" | "ssdb"
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/urls.htm
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/jdbcthin.htm
"jdbc:oracle:thin:orcluser/PW@localhost:55:orclsn" | null | "oracle" | "thin" | "orcluser" | "localhost" | 55 | "orclsn" | null
"jdbc:oracle:thin:orcluser/PW@//orcl.host:55/orclsn" | null | "oracle" | "thin" | "orcluser" | "orcl.host" | 55 | "orclsn" | null
"jdbc:oracle:thin:orcluser/PW@127.0.0.1:orclsn" | null | "oracle" | "thin" | "orcluser" | "127.0.0.1" | 1521 | "orclsn" | null
"jdbc:oracle:thin:orcluser/PW@//orcl.host/orclsn" | null | "oracle" | "thin" | "orcluser" | "orcl.host" | 1521 | "orclsn" | null
"jdbc:oracle:thin:@//orcl.host:55/orclsn" | null | "oracle" | "thin" | null | "orcl.host" | 55 | "orclsn" | null
"jdbc:oracle:thin:@ldap://orcl.host:55/some,cn=OracleContext,dc=com" | null | "oracle" | "thin" | null | "orcl.host" | 55 | "some,cn=oraclecontext,dc=com" | null
"jdbc:oracle:thin:127.0.0.1:orclsn" | null | "oracle" | "thin" | null | "127.0.0.1" | 1521 | "orclsn" | null
"jdbc:oracle:thin:orcl.host:orclsn" | stdProps | "oracle" | "thin" | "stdUserName" | "orcl.host" | 9999 | "orclsn" | "stdDatabaseName"
"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST= 127.0.0.1 )(POR T= 666))" +
"(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orclsn)))" | null | "oracle" | "thin" | null | "127.0.0.1" | 1521 | "orclsn" | null
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/instclnt.htm
"jdbc:oracle:drivertype:orcluser/PW@orcl.host:55/orclsn" | null | "oracle" | "drivertype" | "orcluser" | "orcl.host" | 55 | "orclsn" | null
"jdbc:oracle:oci8:@" | null | "oracle" | "oci8" | null | null | 1521 | null | null
"jdbc:oracle:oci8:@" | stdProps | "oracle" | "oci8" | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
"jdbc:oracle:oci8:@orclsn" | null | "oracle" | "oci8" | null | null | 1521 | "orclsn" | null
"jdbc:oracle:oci:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)( HOST = orcl.host )" +
"( PORT = 55 ))(CONNECT_DATA=(SERVICE_NAME =orclsn )))" | null | "oracle" | "oci" | null | "orcl.host" | 55 | "orclsn" | null
// https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/java/src/tpc/imjcc_tjvjcccn.html
// https://www.ibm.com/support/knowledgecenter/en/SSEPGG_10.5.0/com.ibm.db2.luw.apdv.java.doc/src/tpc/imjcc_r0052342.html
"jdbc:db2://db2.host" | null | "db2" | null | null | "db2.host" | 50000 | null | null
"jdbc:db2://db2.host" | stdProps | "db2" | null | "stdUserName" | "db2.host" | 9999 | null | "stdDatabaseName"
"jdbc:db2://db2.host:77/db2db:user=db2user;password=PW;" | null | "db2" | null | "db2user" | "db2.host" | 77 | "db2db" | null
"jdbc:db2://db2.host:77/db2db:user=db2user;password=PW;" | stdProps | "db2" | null | "db2user" | "db2.host" | 77 | "db2db" | "stdDatabaseName"
"jdbc:as400://ashost:66/asdb:user=asuser;password=PW;" | null | "as400" | null | "asuser" | "ashost" | 66 | "asdb" | null
// https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.03/en-US/ff15928cf5594d78b841fbbe649f04b4.html
"jdbc:sap://sap.host" | null | "sap" | null | null | "sap.host" | null | null | null
"jdbc:sap://sap.host" | stdProps | "sap" | null | "stdUserName" | "sap.host" | 9999 | null | "stdDatabaseName"
"jdbc:sap://sap.host:88/?databaseName=sapdb&user=sapuser&password=PW" | null | "sap" | null | "sapuser" | "sap.host" | 88 | null | "sapdb"
// TODO:
// "jdbc:informix-sqli://infxhost:99/infxdb:INFORMIXSERVER=infxsn;user=infxuser;password=PW" | null | "informix-sqli" | null | "infxuser" | "infxhost" | 99 | "infxdb"| null
// "jdbc:informix-direct://infxdb:999;user=infxuser;password=PW" | null | "informix-direct" | null | "infxuser" | "infxhost" | 999 | "infxdb"| null
// http://www.h2database.com/html/features.html#database_url
"jdbc:h2:mem:" | null | "h2" | "mem" | null | null | null | null | null
"jdbc:h2:mem:" | stdProps | "h2" | "mem" | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
"jdbc:h2:mem:h2db" | null | "h2" | "mem" | null | null | null | "h2db" | null
"jdbc:h2:tcp://h2.host:111/path/h2db;user=h2user;password=PW" | null | "h2" | "tcp" | "h2user" | "h2.host" | 111 | "path/h2db" | null
"jdbc:h2:ssl://h2.host:111/path/h2db;user=h2user;password=PW" | null | "h2" | "ssl" | "h2user" | "h2.host" | 111 | "path/h2db" | null
"jdbc:h2:/data/h2file" | null | "h2" | "file" | null | null | null | "/data/h2file" | null
"jdbc:h2:file:~/h2file;USER=h2user;PASSWORD=PW" | null | "h2" | "file" | null | null | null | "~/h2file" | null
"jdbc:h2:file:/data/h2file" | null | "h2" | "file" | null | null | null | "/data/h2file" | null
"jdbc:h2:file:C:/data/h2file" | null | "h2" | "file" | null | null | null | "c:/data/h2file" | null
"jdbc:h2:zip:~/db.zip!/h2zip" | null | "h2" | "zip" | null | null | null | "~/db.zip!/h2zip" | null
// http://hsqldb.org/doc/2.0/guide/dbproperties-chapt.html
"jdbc:hsqldb:hsdb" | null | "hsqldb" | "mem" | "SA" | null | null | "hsdb" | null
"jdbc:hsqldb:hsdb" | stdProps | "hsqldb" | "mem" | "stdUserName" | "stdServerName" | 9999 | "hsdb" | "stdDatabaseName"
"jdbc:hsqldb:mem:hsdb" | null | "hsqldb" | "mem" | "SA" | null | null | "hsdb" | null
"jdbc:hsqldb:file:hsdb" | null | "hsqldb" | "file" | "SA" | null | null | "hsdb" | null
"jdbc:hsqldb:file:/loc/hsdb" | null | "hsqldb" | "file" | "SA" | null | null | "/loc/hsdb" | null
"jdbc:hsqldb:file:C:/hsdb" | null | "hsqldb" | "file" | "SA" | null | null | "c:/hsdb" | null
"jdbc:hsqldb:res:hsdb" | null | "hsqldb" | "res" | "SA" | null | null | "hsdb" | null
"jdbc:hsqldb:res:/cp/hsdb" | null | "hsqldb" | "res" | "SA" | null | null | "/cp/hsdb" | null
"jdbc:hsqldb:hsql://hs.host:333/hsdb" | null | "hsqldb" | "hsql" | "SA" | "hs.host" | 333 | "hsdb" | null
"jdbc:hsqldb:hsqls://hs.host/hsdb" | null | "hsqldb" | "hsqls" | "SA" | "hs.host" | 9001 | "hsdb" | null
"jdbc:hsqldb:http://hs.host" | null | "hsqldb" | "http" | "SA" | "hs.host" | 80 | null | null
"jdbc:hsqldb:http://hs.host:333/hsdb" | null | "hsqldb" | "http" | "SA" | "hs.host" | 333 | "hsdb" | null
"jdbc:hsqldb:https://127.0.0.1/hsdb" | null | "hsqldb" | "https" | "SA" | "127.0.0.1" | 443 | "hsdb" | null
// https://db.apache.org/derby/papers/DerbyClientSpec.html#Connection+URL+Format
// https://db.apache.org/derby/docs/10.8/devguide/cdevdvlp34964.html
"jdbc:derby:derbydb" | null | "derby" | "directory" | "APP" | null | null | "derbydb" | null
"jdbc:derby:derbydb" | stdProps | "derby" | "directory" | "stdUserName" | "stdServerName" | 9999 | "derbydb" | "stdDatabaseName"
"jdbc:derby:derbydb;user=derbyuser;password=pw" | null | "derby" | "directory" | "derbyuser" | null | null | "derbydb" | null
"jdbc:derby:memory:derbydb" | null | "derby" | "memory" | "APP" | null | null | "derbydb" | null
"jdbc:derby:memory:;databaseName=derbydb" | null | "derby" | "memory" | "APP" | null | null | null | "derbydb"
"jdbc:derby:memory:derbydb;databaseName=altdb" | null | "derby" | "memory" | "APP" | null | null | "derbydb" | "altdb"
"jdbc:derby:memory:derbydb;user=derbyuser;password=pw" | null | "derby" | "memory" | "derbyuser" | null | null | "derbydb" | null
"jdbc:derby://derby.host:222/memory:derbydb;create=true" | null | "derby" | "network" | "APP" | "derby.host" | 222 | "derbydb" | null
"jdbc:derby://derby.host/memory:derbydb;create=true;user=derbyuser;password=pw" | null | "derby" | "network" | "derbyuser" | "derby.host" | 1527 | "derbydb" | null
"jdbc:derby://127.0.0.1:1527/memory:derbydb;create=true;user=derbyuser;password=pw" | null | "derby" | "network" | "derbyuser" | "127.0.0.1" | 1527 | "derbydb" | null
"jdbc:derby:directory:derbydb;user=derbyuser;password=pw" | null | "derby" | "directory" | "derbyuser" | null | null | "derbydb" | null
"jdbc:derby:classpath:/some/derbydb;user=derbyuser;password=pw" | null | "derby" | "classpath" | "derbyuser" | null | null | "/some/derbydb" | null
"jdbc:derby:jar:/derbydb;user=derbyuser;password=pw" | null | "derby" | "jar" | "derbyuser" | null | null | "/derbydb" | null
"jdbc:derby:jar:(~/path/to/db.jar)/other/derbydb;user=derbyuser;password=pw" | null | "derby" | "jar" | "derbyuser" | null | null | "(~/path/to/db.jar)/other/derbydb" | null
expected = new DBInfo.Builder().type(type).subtype(subtype).user(user).instance(instance).db(db).host(host).port(port).build()
}
}

View File

@ -4,13 +4,13 @@ import com.zaxxer.hikari.HikariDataSource
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.api.DDSpanTypes
import io.opentracing.tag.Tags
import javax.sql.DataSource
import org.apache.derby.jdbc.EmbeddedDriver
import org.h2.Driver
import org.hsqldb.jdbc.JDBCDriver
import spock.lang.Shared
import spock.lang.Unroll
import javax.sql.DataSource
import java.sql.CallableStatement
import java.sql.Connection
import java.sql.PreparedStatement
@ -26,32 +26,42 @@ class JDBCInstrumentationTest extends AgentTestRunner {
@Shared
private Map<String, String> jdbcUrls = [
h2 : "jdbc:h2:mem:" + dbName,
derby : "jdbc:derby:memory:" + dbName,
hsqldb: "jdbc:hsqldb:mem:" + dbName
"h2" : "jdbc:h2:mem:$dbName",
"derby" : "jdbc:derby:memory:$dbName",
"hsqldb": "jdbc:hsqldb:mem:$dbName",
]
@Shared
private Map<String, String> jdbcDriverClassNames = [
h2 : "org.h2.Driver",
derby : "org.apache.derby.jdbc.EmbeddedDriver",
hsqldb: "org.hsqldb.jdbc.JDBCDriver"
"h2" : "org.h2.Driver",
"derby" : "org.apache.derby.jdbc.EmbeddedDriver",
"hsqldb": "org.hsqldb.jdbc.JDBCDriver",
]
@Shared
private Map<String, String> jdbcUserNames = [
h2 : null,
derby : "APP",
hsqldb: "SA"
"h2" : null,
"derby" : "APP",
"hsqldb": "SA",
]
@Shared
private Properties connectionProps = {
def props = new Properties()
// props.put("user", "someUser")
// props.put("password", "somePassword")
props.put("databaseName", "someDb")
props.put("OPEN_NEW", "true") // So H2 doesn't complain about username/password.
return props
}()
// JDBC Connection pool name (i.e. HikariCP) -> Map<dbName, Datasource>
@Shared
private Map<String, Map<String, DataSource>> cpDatasources = new HashMap<>()
def prepareConnectionPoolDatasources() {
String[] connectionPoolNames = [
"tomcat", "hikari", "c3p0"
"tomcat", "hikari", "c3p0",
]
connectionPoolNames.each {
cpName ->
@ -170,7 +180,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
}
"span.kind" Tags.SPAN_KIND_CLIENT
"component" "java-jdbc-statement"
"db.instance" jdbcUrls.get(driver)
"db.instance" dbName.toLowerCase()
"span.origin.type" String
defaultTags()
}
@ -183,19 +193,22 @@ class JDBCInstrumentationTest extends AgentTestRunner {
connection.close()
where:
driver | connection | username | query
"h2" | new Driver().connect(jdbcUrls.get("h2"), null) | null | "SELECT 3"
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), null) | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null) | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
"h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "SELECT 3"
"derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | cpDatasources.get("tomcat").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
"h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "SELECT 3"
"derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | cpDatasources.get("hikari").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
"h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "SELECT 3"
"derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | cpDatasources.get("c3p0").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
driver | connection | username | query
"h2" | new Driver().connect(jdbcUrls.get("h2"), null) | null | "SELECT 3"
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), null) | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null) | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
"h2" | new Driver().connect(jdbcUrls.get("h2"), connectionProps) | null | "SELECT 3"
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), connectionProps) | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | new JDBCDriver().connect(jdbcUrls.get("hsqldb"), connectionProps) | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
"h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "SELECT 3"
"derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | cpDatasources.get("tomcat").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
"h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "SELECT 3"
"derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | cpDatasources.get("hikari").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
"h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "SELECT 3"
"derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | cpDatasources.get("c3p0").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
}
@Unroll
@ -230,7 +243,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
}
"span.kind" Tags.SPAN_KIND_CLIENT
"component" "java-jdbc-prepared_statement"
"db.instance" jdbcUrls.get(driver)
"db.instance" dbName.toLowerCase()
"span.origin.type" String
defaultTags()
}
@ -285,7 +298,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
}
"span.kind" Tags.SPAN_KIND_CLIENT
"component" "java-jdbc-prepared_statement"
"db.instance" jdbcUrls.get(driver)
"db.instance" dbName.toLowerCase()
"span.origin.type" String
defaultTags()
}
@ -340,7 +353,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
}
"span.kind" Tags.SPAN_KIND_CLIENT
"component" "java-jdbc-prepared_statement"
"db.instance" jdbcUrls.get(driver)
"db.instance" dbName.toLowerCase()
"span.origin.type" String
defaultTags()
}
@ -395,7 +408,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
}
"span.kind" Tags.SPAN_KIND_CLIENT
"component" "java-jdbc-statement"
"db.instance" jdbcUrls.get(driver)
"db.instance" dbName.toLowerCase()
"span.origin.type" String
defaultTags()
}
@ -453,7 +466,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
}
"span.kind" Tags.SPAN_KIND_CLIENT
"component" "java-jdbc-prepared_statement"
"db.instance" jdbcUrls.get(driver)
"db.instance" dbName.toLowerCase()
"span.origin.type" String
defaultTags()
}
@ -528,7 +541,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
"component" "java-jdbc-statement"
}
"span.kind" Tags.SPAN_KIND_CLIENT
"db.instance" jdbcUrls.get(driver)
"db.instance" dbName.toLowerCase()
"span.origin.type" String
defaultTags()
}
@ -585,7 +598,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
res[i] == 3
}
assertTraces(5) {
trace(0, 2) {
trace(0, 1) {
span(0) {
operationName "${dbType}.query"
serviceName dbType
@ -597,24 +610,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
"db.user" "SA"
"component" "java-jdbc-prepared_statement"
"span.kind" Tags.SPAN_KIND_CLIENT
"db.instance" jdbcUrls.get(dbType)
"span.origin.type" String
defaultTags()
}
}
span(1) {
operationName "${dbType}.query"
serviceName dbType
resourceName "CALL USER()"
spanType DDSpanTypes.SQL
errored false
childOf(span(0))
tags {
"db.type" "hsqldb"
"db.user" "SA"
"component" "java-jdbc-statement"
"span.kind" Tags.SPAN_KIND_CLIENT
"db.instance" jdbcUrls.get(dbType)
"db.instance" dbName.toLowerCase()
"span.origin.type" String
defaultTags()
}
@ -633,7 +629,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
"db.user" "SA"
"component" "java-jdbc-prepared_statement"
"span.kind" Tags.SPAN_KIND_CLIENT
"db.instance" jdbcUrls.get(dbType)
"db.instance" dbName.toLowerCase()
"span.origin.type" String
defaultTags()
}

View File

@ -43,7 +43,7 @@ public class LettuceClientDecorator extends DatabaseClientDecorator<RedisURI> {
@Override
protected String dbInstance(final RedisURI connection) {
return connection.getHost() + ":" + connection.getPort() + "/" + connection.getDatabase();
return null;
}
@Override
@ -53,7 +53,8 @@ public class LettuceClientDecorator extends DatabaseClientDecorator<RedisURI> {
Tags.PEER_PORT.set(span, connection.getPort());
span.setTag("db.redis.dbIndex", connection.getDatabase());
span.setTag(DDTags.RESOURCE_NAME, "CONNECT:" + dbInstance(connection));
span.setTag(DDTags.RESOURCE_NAME, "CONNECT:" + connection.getHost()
+ ":" + connection.getPort() + "/" + connection.getDatabase());
}
return super.onConnection(span, connection);
}

View File

@ -123,7 +123,6 @@ class LettuceAsyncClientTest extends AgentTestRunner {
tags {
defaultTags()
"component" "redis-client"
"db.instance" dbAddr
"db.redis.dbIndex" 0
"db.type" "redis"
"peer.hostname" HOST
@ -163,7 +162,6 @@ class LettuceAsyncClientTest extends AgentTestRunner {
tags {
defaultTags()
"component" "redis-client"
"db.instance" dbAddrNonExistent
"db.redis.dbIndex" 0
"db.type" "redis"
errorTags CompletionException, String

View File

@ -103,7 +103,6 @@ class LettuceSyncClientTest extends AgentTestRunner {
tags {
defaultTags()
"component" "redis-client"
"db.instance" dbAddr
"db.redis.dbIndex" 0
"db.type" "redis"
"peer.hostname" HOST
@ -140,7 +139,6 @@ class LettuceSyncClientTest extends AgentTestRunner {
tags {
defaultTags()
"component" "redis-client"
"db.instance" dbAddrNonExistent
"db.redis.dbIndex" 0
"db.type" "redis"
errorTags CompletionException, String

View File

@ -1,45 +0,0 @@
import datadog.trace.api.DDTags
import io.opentracing.Span
import io.opentracing.tag.Tags
import org.bson.BsonArray
import org.bson.BsonDocument
import org.bson.BsonString
import spock.lang.Shared
import spock.lang.Specification
import static datadog.trace.instrumentation.mongo.MongoClientDecorator.DECORATE
class MongoClientDecoratorTest extends Specification {
@Shared
def query1, query2
def setupSpec() {
query1 = new BsonDocument("find", new BsonString("show"))
query1.put("stuff", new BsonString("secret"))
query2 = new BsonDocument("insert", new BsonString("table"))
def nestedDoc = new BsonDocument("count", new BsonString("show"))
nestedDoc.put("id", new BsonString("secret"))
query2.put("docs", new BsonArray(Arrays.asList(new BsonString("secret"), nestedDoc)))
}
def "test query scrubbing"() {
setup:
def span = Mock(Span)
// all "secret" strings should be scrubbed out of these queries
when:
DECORATE.onStatement(span, query)
then:
1 * span.setTag(Tags.DB_STATEMENT.key, expected)
1 * span.setTag(DDTags.RESOURCE_NAME, expected)
0 * _
where:
query << [query1, query2]
expected = query.toString().replaceAll("secret", "?")
}
}

View File

@ -1,65 +0,0 @@
package datadog.trace.instrumentation.mongo;
import static org.assertj.core.api.Java6Assertions.assertThat;
import com.mongodb.ServerAddress;
import com.mongodb.connection.ClusterId;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ServerId;
import com.mongodb.event.CommandStartedEvent;
import datadog.opentracing.DDSpan;
import datadog.opentracing.DDTracer;
import datadog.trace.api.DDSpanTypes;
import io.opentracing.tag.Tags;
import java.util.Arrays;
import java.util.List;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.junit.Test;
public class MongoClientInstrumentationTest {
private static ConnectionDescription makeConnection() {
return new ConnectionDescription(new ServerId(new ClusterId(), new ServerAddress()));
}
@Test
public void mongoSpan() {
final CommandStartedEvent cmd =
new CommandStartedEvent(1, makeConnection(), "databasename", "query", new BsonDocument());
final DDSpan span = new DDTracer().buildSpan("foo").start();
MongoClientDecorator.DECORATE.afterStart(span);
MongoClientDecorator.DECORATE.onStatement(span, cmd.getCommand());
assertThat(span.context().getSpanType()).isEqualTo("mongodb");
assertThat(span.context().getResourceName())
.isEqualTo(span.context().getTags().get("db.statement"));
assertThat(span.getSpanType()).isEqualTo(DDSpanTypes.MONGO);
}
@Test
public void queryScrubbing() {
// all "secret" strings should be scrubbed out of these queries
final BsonDocument query1 = new BsonDocument("find", new BsonString("show"));
query1.put("stuff", new BsonString("secret"));
final BsonDocument query2 = new BsonDocument("insert", new BsonString("table"));
final BsonDocument query2_1 = new BsonDocument("count", new BsonString("show"));
query2_1.put("id", new BsonString("secret"));
query2.put("docs", new BsonArray(Arrays.asList(new BsonString("secret"), query2_1)));
final List<BsonDocument> queries = Arrays.asList(query1, query2);
for (final BsonDocument query : queries) {
final CommandStartedEvent cmd =
new CommandStartedEvent(1, makeConnection(), "databasename", "query", query);
final DDSpan span = new DDTracer().buildSpan("foo").start();
MongoClientDecorator.DECORATE.afterStart(span);
MongoClientDecorator.DECORATE.onStatement(span, cmd.getCommand());
assertThat(span.getSpanType()).isEqualTo(DDSpanTypes.MONGO);
assertThat(span.getTags().get(Tags.DB_STATEMENT.getKey()))
.isEqualTo(query.toString().replaceAll("secret", "?"));
}
}
}

View File

@ -1,27 +0,0 @@
muzzle {
pass {
group = "org.mongodb"
module = "mongodb-driver-async"
versions = "[3.3,)"
extraDependency 'org.mongodb:mongo-java-driver:3.3.0'
assertInverse = true
}
}
apply from: "${rootDir}/gradle/java.gradle"
dependencies {
// use mongo listener
compile(project(':dd-java-agent:instrumentation:mongo-3.1')) {
transitive = false
}
compileOnly group: 'org.mongodb', name: 'mongo-java-driver', version: '3.3.0'
compileOnly group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.3.0'
compile project(':dd-java-agent:agent-tooling')
compile deps.bytebuddy
compile deps.opentracing
annotationProcessor deps.autoservice
implementation deps.autoservice
}

View File

@ -28,9 +28,8 @@ dependencies {
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
testCompile group: 'org.assertj', name: 'assertj-core', version: '2.9.+'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.19.0'
testCompile project(':dd-java-agent:instrumentation:mongo').sourceSets.test.output
testCompile group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '1.50.5'
testCompile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.1.0'
latestDepTestCompile group: 'org.mongodb', name: 'mongo-java-driver', version: '+'

View File

@ -1,5 +1,9 @@
package datadog.trace.instrumentation.mongo;
import com.mongodb.connection.ClusterId;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ConnectionId;
import com.mongodb.connection.ServerId;
import com.mongodb.event.CommandStartedEvent;
import datadog.trace.agent.decorator.DatabaseClientDecorator;
import datadog.trace.api.DDSpanTypes;
@ -48,19 +52,25 @@ public class MongoClientDecorator extends DatabaseClientDecorator<CommandStarted
@Override
protected String dbInstance(final CommandStartedEvent event) {
// Use description if set.
final ConnectionDescription connectionDescription = event.getConnectionDescription();
if (connectionDescription != null) {
final ConnectionId connectionId = connectionDescription.getConnectionId();
if (connectionId != null) {
final ServerId serverId = connectionId.getServerId();
if (serverId != null) {
ClusterId clusterId = serverId.getClusterId();
if (clusterId != null) {
String description = clusterId.getDescription();
if (description != null) {
return description;
}
}
}
}
}
// Fallback to db name.
return event.getDatabaseName();
// This would be the "proper" db.instance:
// final ConnectionDescription connectionDescription = event.getConnectionDescription();
// if (connectionDescription != null) {
// final ConnectionId connectionId = connectionDescription.getConnectionId();
// if (connectionId != null) {
// final ServerId serverId = connectionId.getServerId();
// if (serverId != null) {
// return serverId.toString();
// }
// }
// }
// return null;
}
public Span onStatement(final Span span, final BsonDocument statement) {

View File

@ -0,0 +1,260 @@
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 datadog.opentracing.DDSpan
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.api.DDSpanTypes
import io.opentracing.tag.Tags
import org.bson.BsonDocument
import org.bson.BsonString
import org.bson.Document
import spock.lang.Shared
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
class MongoClientTest extends MongoBaseTest {
@Shared
MongoClient client
def setup() throws Exception {
client = new MongoClient(new ServerAddress("localhost", port),
MongoClientOptions.builder()
.description("some-description")
.build())
}
def cleanup() throws Exception {
client?.close()
client = null
}
def "test create collection"() {
setup:
MongoDatabase db = client.getDatabase(dbName)
when:
db.createCollection(collectionName)
then:
assertTraces(1) {
trace(0, 1) {
mongoSpan(it, 0, "{\"create\":\"$collectionName\",\"capped\":\"?\"}")
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test create collection no description"() {
setup:
MongoDatabase db = new MongoClient("localhost", port).getDatabase(dbName)
when:
db.createCollection(collectionName)
then:
assertTraces(1) {
trace(0, 1) {
mongoSpan(it, 0, "{\"create\":\"$collectionName\",\"capped\":\"?\"}", dbName)
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test get collection"() {
setup:
MongoDatabase db = client.getDatabase(dbName)
when:
int count = db.getCollection(collectionName).count()
then:
count == 0
assertTraces(1) {
trace(0, 1) {
mongoSpan(it, 0, "{\"count\":\"$collectionName\",\"query\":{}}")
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test insert"() {
setup:
MongoCollection<Document> collection = runUnderTrace("setup") {
MongoDatabase db = client.getDatabase(dbName)
db.createCollection(collectionName)
return db.getCollection(collectionName)
}
TEST_WRITER.waitForTraces(1)
TEST_WRITER.clear()
when:
collection.insertOne(new Document("password", "SECRET"))
then:
collection.count() == 1
assertTraces(2) {
trace(0, 1) {
mongoSpan(it, 0, "{\"insert\":\"$collectionName\",\"ordered\":\"?\",\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}")
}
trace(1, 1) {
mongoSpan(it, 0, "{\"count\":\"$collectionName\",\"query\":{}}")
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test update"() {
setup:
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
}
TEST_WRITER.waitForTraces(1)
TEST_WRITER.clear()
when:
def result = collection.updateOne(
new BsonDocument("password", new BsonString("OLDPW")),
new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW"))))
then:
result.modifiedCount == 1
collection.count() == 1
assertTraces(2) {
trace(0, 1) {
mongoSpan(it, 0, "{\"update\":\"?\",\"ordered\":\"?\",\"updates\":[{\"q\":{\"password\":\"?\"},\"u\":{\"\$set\":{\"password\":\"?\"}}}]}")
}
trace(1, 1) {
mongoSpan(it, 0, "{\"count\":\"$collectionName\",\"query\":{}}")
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test delete"() {
setup:
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
}
TEST_WRITER.waitForTraces(1)
TEST_WRITER.clear()
when:
def result = collection.deleteOne(new BsonDocument("password", new BsonString("SECRET")))
then:
result.deletedCount == 1
collection.count() == 0
assertTraces(2) {
trace(0, 1) {
mongoSpan(it, 0, "{\"delete\":\"?\",\"ordered\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}")
}
trace(1, 1) {
mongoSpan(it, 0, "{\"count\":\"$collectionName\",\"query\":{}}")
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test error"() {
setup:
MongoCollection<Document> collection = runUnderTrace("setup") {
MongoDatabase db = client.getDatabase(dbName)
db.createCollection(collectionName)
return db.getCollection(collectionName)
}
TEST_WRITER.waitForTraces(1)
TEST_WRITER.clear()
when:
collection.updateOne(new BsonDocument(), new BsonDocument())
then:
thrown(IllegalArgumentException)
// Unfortunately not caught by our instrumentation.
assertTraces(0) {}
where:
dbName = "test_db"
collectionName = "testCollection"
}
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 = "testCollection"
}
def mongoSpan(TraceAssert trace, int index, String statement, String instance = "some-description", Object parentSpan = null, Throwable exception = null) {
trace.span(index) {
serviceName "mongo"
operationName "mongo.query"
resourceName {
assert it.replace(" ", "") == statement
return true
}
spanType DDSpanTypes.MONGO
if (parentSpan == null) {
parent()
} else {
childOf((DDSpan) parentSpan)
}
tags {
"$Tags.COMPONENT.key" "java-mongo"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.DB_INSTANCE.key" instance
"$Tags.DB_STATEMENT.key" {
it.replace(" ", "") == statement
}
"$Tags.DB_TYPE.key" "mongo"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" port
defaultTags()
}
}
}
}

View File

@ -0,0 +1,47 @@
// Set properties before any plugins get loaded
ext {
// Since we're using CompletableFutures in the test...
minJavaVersionForTests = JavaVersion.VERSION_1_8
}
muzzle {
pass {
group = "org.mongodb"
module = "mongodb-driver-async"
versions = "[3.3,)"
extraDependency 'org.mongodb:mongo-java-driver:3.3.0'
assertInverse = true
}
}
apply from: "${rootDir}/gradle/java.gradle"
apply plugin: 'org.unbroken-dome.test-sets'
testSets {
latestDepTest {
dirName = 'test'
}
}
dependencies {
// use mongo listener
compile(project(':dd-java-agent:instrumentation:mongo:driver-3.1')) {
transitive = false
}
compileOnly group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.3.0'
compile project(':dd-java-agent:agent-tooling')
compile deps.bytebuddy
compile deps.opentracing
annotationProcessor deps.autoservice
implementation deps.autoservice
testCompile project(':dd-java-agent:testing')
testCompile project(':dd-java-agent:instrumentation:mongo').sourceSets.test.output
testCompile group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '1.50.5'
testCompile group: 'org.mongodb', name: 'mongodb-driver-async', version: '3.3.0'
latestDepTestCompile group: 'org.mongodb', name: 'mongodb-driver-async', version: '+'
}

View File

@ -0,0 +1,298 @@
import com.mongodb.ConnectionString
import com.mongodb.async.SingleResultCallback
import com.mongodb.async.client.MongoClient
import com.mongodb.async.client.MongoClientSettings
import com.mongodb.async.client.MongoClients
import com.mongodb.async.client.MongoCollection
import com.mongodb.async.client.MongoDatabase
import com.mongodb.client.result.DeleteResult
import com.mongodb.client.result.UpdateResult
import com.mongodb.connection.ClusterSettings
import datadog.opentracing.DDSpan
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.api.DDSpanTypes
import io.opentracing.tag.Tags
import org.bson.BsonDocument
import org.bson.BsonString
import org.bson.Document
import spock.lang.Shared
import spock.lang.Timeout
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CountDownLatch
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
@Timeout(10)
class MongoAsyncClientTest extends MongoBaseTest {
@Shared
MongoClient client
def setup() throws Exception {
client = MongoClients.create(
MongoClientSettings.builder()
.clusterSettings(
ClusterSettings.builder()
.description("some-description")
.applyConnectionString(new ConnectionString("mongodb://localhost:$port"))
.build())
.build())
}
def cleanup() throws Exception {
client?.close()
client = null
}
def "test create collection"() {
setup:
MongoDatabase db = client.getDatabase(dbName)
when:
db.createCollection(collectionName, toCallback {})
then:
assertTraces(1) {
trace(0, 1) {
mongoSpan(it, 0) {
assert it.replaceAll(" ", "") == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" ||
it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
true
}
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test create collection no description"() {
setup:
MongoDatabase db = MongoClients.create("mongodb://localhost:$port").getDatabase(dbName)
when:
db.createCollection(collectionName, toCallback {})
then:
assertTraces(1) {
trace(0, 1) {
mongoSpan(it, 0, {
assert it.replaceAll(" ", "") == "{\"create\":\"$collectionName\",\"capped\":\"?\"}" ||
it == "{\"create\": \"$collectionName\", \"capped\": \"?\", \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
true
}, dbName)
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test get collection"() {
setup:
MongoDatabase db = client.getDatabase(dbName)
when:
def count = new CompletableFuture()
db.getCollection(collectionName).count toCallback { count.complete(it) }
then:
count.get() == 0
assertTraces(1) {
trace(0, 1) {
mongoSpan(it, 0) {
assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" ||
it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
true
}
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test insert"() {
setup:
MongoCollection<Document> collection = runUnderTrace("setup") {
MongoDatabase db = client.getDatabase(dbName)
def latch1 = new CountDownLatch(1)
db.createCollection(collectionName, toCallback { latch1.countDown() })
latch1.await()
return db.getCollection(collectionName)
}
TEST_WRITER.waitForTraces(1)
TEST_WRITER.clear()
when:
def count = new CompletableFuture()
collection.insertOne(new Document("password", "SECRET"), toCallback {
collection.count toCallback { count.complete(it) }
})
then:
count.get() == 1
assertTraces(2) {
trace(0, 1) {
mongoSpan(it, 0) {
assert it.replaceAll(" ", "") == "{\"insert\":\"$collectionName\",\"ordered\":\"?\",\"documents\":[{\"_id\":\"?\",\"password\":\"?\"}]}" ||
it == "{\"insert\": \"$collectionName\", \"ordered\": \"?\", \"\$db\": \"?\", \"documents\": [{\"_id\": \"?\", \"password\": \"?\"}]}"
true
}
}
trace(1, 1) {
mongoSpan(it, 0) {
assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" ||
it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
true
}
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test update"() {
setup:
MongoCollection<Document> collection = runUnderTrace("setup") {
MongoDatabase db = client.getDatabase(dbName)
def latch1 = new CountDownLatch(1)
db.createCollection(collectionName, toCallback { latch1.countDown() })
latch1.await()
def coll = db.getCollection(collectionName)
def latch2 = new CountDownLatch(1)
coll.insertOne(new Document("password", "OLDPW"), toCallback { latch2.countDown() })
latch2.await()
return coll
}
TEST_WRITER.waitForTraces(1)
TEST_WRITER.clear()
when:
def result = new CompletableFuture<UpdateResult>()
def count = new CompletableFuture()
collection.updateOne(
new BsonDocument("password", new BsonString("OLDPW")),
new BsonDocument('$set', new BsonDocument("password", new BsonString("NEWPW"))), toCallback {
result.complete(it)
collection.count toCallback { count.complete(it) }
})
then:
result.get().modifiedCount == 1
count.get() == 1
assertTraces(2) {
trace(0, 1) {
mongoSpan(it, 0) {
assert it.replaceAll(" ", "") == "{\"update\":\"?\",\"ordered\":\"?\",\"updates\":[{\"q\":{\"password\":\"?\"},\"u\":{\"\$set\":{\"password\":\"?\"}}}]}" ||
it == "{\"update\": \"?\", \"ordered\": \"?\", \"\$db\": \"?\", \"updates\": [{\"q\": {\"password\": \"?\"}, \"u\": {\"\$set\": {\"password\": \"?\"}}}]}"
true
}
}
trace(1, 1) {
mongoSpan(it, 0) {
assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" ||
it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
true
}
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
def "test delete"() {
setup:
MongoCollection<Document> collection = runUnderTrace("setup") {
MongoDatabase db = client.getDatabase(dbName)
def latch1 = new CountDownLatch(1)
db.createCollection(collectionName, toCallback { latch1.countDown() })
latch1.await()
def coll = db.getCollection(collectionName)
def latch2 = new CountDownLatch(1)
coll.insertOne(new Document("password", "SECRET"), toCallback { latch2.countDown() })
latch2.await()
return coll
}
TEST_WRITER.waitForTraces(1)
TEST_WRITER.clear()
when:
def result = new CompletableFuture<DeleteResult>()
def count = new CompletableFuture()
collection.deleteOne(new BsonDocument("password", new BsonString("SECRET")), toCallback {
result.complete(it)
collection.count toCallback { count.complete(it) }
})
then:
result.get().deletedCount == 1
count.get() == 0
assertTraces(2) {
trace(0, 1) {
mongoSpan(it, 0) {
assert it.replaceAll(" ", "") == "{\"delete\":\"?\",\"ordered\":\"?\",\"deletes\":[{\"q\":{\"password\":\"?\"},\"limit\":\"?\"}]}" ||
it == "{\"delete\": \"?\", \"ordered\": \"?\", \"\$db\": \"?\", \"deletes\": [{\"q\": {\"password\": \"?\"}, \"limit\": \"?\"}]}"
true
}
}
trace(1, 1) {
mongoSpan(it, 0) {
assert it.replaceAll(" ", "") == "{\"count\":\"$collectionName\",\"query\":{}}" ||
it == "{\"count\": \"$collectionName\", \"query\": {}, \"\$db\": \"?\", \"\$readPreference\": {\"mode\": \"?\"}}"
true
}
}
}
where:
dbName = "test_db"
collectionName = "testCollection"
}
SingleResultCallback toCallback(Closure closure) {
return new SingleResultCallback() {
@Override
void onResult(Object result, Throwable t) {
if (t) {
closure.call(t)
} else {
closure.call(result)
}
}
}
}
def mongoSpan(TraceAssert trace, int index, Closure<Boolean> statementEval, String instance = "some-description", Object parentSpan = null, Throwable exception = null) {
trace.span(index) {
serviceName "mongo"
operationName "mongo.query"
resourceName statementEval
spanType DDSpanTypes.MONGO
if (parentSpan == null) {
parent()
} else {
childOf((DDSpan) parentSpan)
}
tags {
"$Tags.COMPONENT.key" "java-mongo"
"$Tags.SPAN_KIND.key" Tags.SPAN_KIND_CLIENT
"$Tags.DB_INSTANCE.key" instance
"$Tags.DB_STATEMENT.key" statementEval
"$Tags.DB_TYPE.key" "mongo"
"$Tags.PEER_HOSTNAME.key" "localhost"
"$Tags.PEER_HOST_IPV4.key" "127.0.0.1"
"$Tags.PEER_PORT.key" port
defaultTags()
}
}
}
}

View File

@ -0,0 +1,28 @@
apply from: "${rootDir}/gradle/java.gradle"
dependencies {
testAnnotationProcessor deps.autoservice
testImplementation deps.autoservice
testCompile project(':dd-java-agent:testing')
testCompile group: 'de.flapdoodle.embed', name: 'de.flapdoodle.embed.mongo', version: '1.50.5'
}
// Forcing strict test execution order (no parallel execution) to ensure proper mongo executable initialization.
List<Test> testTasks = []
tasks.withType(Test) { Test testTask ->
testTasks.each {
testTask.shouldRunAfter(it)
}
testTasks.add(testTask)
}
subprojects {
afterEvaluate {
tasks.withType(Test) { Test testTask ->
testTasks.each {
testTask.shouldRunAfter(it)
}
testTasks.add(testTask)
}
}
}

View File

@ -0,0 +1,54 @@
import datadog.trace.agent.test.AgentTestRunner
import datadog.trace.agent.test.utils.PortUtils
import de.flapdoodle.embed.mongo.MongodExecutable
import de.flapdoodle.embed.mongo.MongodProcess
import de.flapdoodle.embed.mongo.MongodStarter
import de.flapdoodle.embed.mongo.config.IMongodConfig
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder
import de.flapdoodle.embed.mongo.config.Net
import de.flapdoodle.embed.mongo.distribution.Version
import de.flapdoodle.embed.process.runtime.Network
import spock.lang.Shared
/**
* Testing needs to be in a centralized project.
* If tests in multiple different projects are using embedded mongo,
* they downloader is at risk of a race condition.
*/
class MongoBaseTest extends AgentTestRunner {
// https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo#executable-collision
private static final MongodStarter STARTER = MongodStarter.getDefaultInstance()
@Shared
int port = PortUtils.randomOpenPort()
@Shared
MongodExecutable mongodExe
@Shared
MongodProcess mongod
def setup() throws Exception {
final IMongodConfig mongodConfig =
new MongodConfigBuilder()
.version(Version.Main.PRODUCTION)
.net(new Net("localhost", port, Network.localhostIsIPv6()))
.build()
mongodExe = STARTER.prepare(mongodConfig)
mongod = mongodExe.start()
}
def cleanup() throws Exception {
mongod?.stop()
mongod = null
mongodExe?.stop()
mongodExe = null
}
def "test port open"() {
when:
new Socket("localhost", port)
then:
noExceptionThrown()
}
}

View File

@ -0,0 +1,12 @@
import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import net.bytebuddy.agent.builder.AgentBuilder;
@AutoService(Instrumenter.class)
public class NoOpInstrumentation implements Instrumenter {
@Override
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
return agentBuilder;
}
}

View File

@ -9,6 +9,7 @@ import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
import static org.asynchttpclient.Dsl.asyncHttpClient
@ -65,7 +66,7 @@ class Netty40ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
and:
assertTraces(1) {
trace(0, 2) {
parentSpan(it, 0, thrownException)
basicSpan(it, 0, "parent", thrownException)
span(1) {
operationName "netty.connect"

View File

@ -10,6 +10,7 @@ import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
import static org.asynchttpclient.Dsl.asyncHttpClient
@ -67,7 +68,7 @@ class Netty41ClientTest extends HttpClientTest<NettyHttpClientDecorator> {
and:
assertTraces(1) {
trace(0, 2) {
parentSpan(it, 0, thrownException)
basicSpan(it, 0, "parent", thrownException)
span(1) {
operationName "netty.connect"

View File

@ -41,7 +41,7 @@ public class MemcacheClientDecorator extends DatabaseClientDecorator<MemcachedCo
@Override
protected String dbInstance(final MemcachedConnection connection) {
return connection.connectionsStatus();
return null;
}
public Span onOperation(final Span span, final String methodName) {

View File

@ -635,7 +635,6 @@ class SpymemcachedTest extends AgentTestRunner {
"${Tags.COMPONENT.key}" COMPONENT_NAME
"${Tags.SPAN_KIND.key}" Tags.SPAN_KIND_CLIENT
"${Tags.DB_TYPE.key}" CompletionListener.DB_TYPE
"$Tags.DB_INSTANCE.key" ~/Connection Status \{ \w*\/127.0.0.1:\d+ active: true, authed: true, last read: \d+ ms ago }/
if (error == "canceled") {
"${CompletionListener.DB_COMMAND_CANCELLED}" true

View File

@ -1,122 +0,0 @@
package datadog.trace.agent.integration;
import static datadog.trace.agent.integration.MongoClientInstrumentationTest.MONGO_DB_NAME;
import static datadog.trace.agent.integration.MongoClientInstrumentationTest.MONGO_HOST;
import static datadog.trace.agent.integration.MongoClientInstrumentationTest.MONGO_PORT;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoClient;
import com.mongodb.async.client.MongoClients;
import com.mongodb.async.client.MongoDatabase;
import datadog.opentracing.DDSpan;
import datadog.opentracing.DDTracer;
import datadog.trace.agent.test.IntegrationTestUtils;
import datadog.trace.common.writer.ListWriter;
import io.opentracing.tag.Tags;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bson.Document;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class MongoAsyncClientInstrumentationTest {
private static MongoClient client;
private static final ListWriter writer = new ListWriter();
private static final DDTracer tracer = new DDTracer(writer);
@BeforeClass
public static void setup() throws Exception {
IntegrationTestUtils.registerOrReplaceGlobalTracer(tracer);
MongoClientInstrumentationTest.startLocalMongo();
// Embeded Mongo uses HttpUrlConnection to download things so we have to clear traces before
// going to tests
writer.clear();
client = MongoClients.create("mongodb://" + MONGO_HOST + ":" + MONGO_PORT);
}
@AfterClass
public static void destroy() throws Exception {
if (null != client) {
client.close();
client = null;
}
MongoClientInstrumentationTest.stopLocalMongo();
}
@Test
public void asyncClientHasListener() {
Assert.assertEquals(1, client.getSettings().getCommandListeners().size());
Assert.assertEquals(
"TracingCommandListener",
client.getSettings().getCommandListeners().get(0).getClass().getSimpleName());
}
@Test
public void insertOperation() throws Exception {
final MongoDatabase db = client.getDatabase(MONGO_DB_NAME);
final String collectionName = "asyncCollection";
final AtomicBoolean done = new AtomicBoolean(false);
db.createCollection(
collectionName,
new SingleResultCallback<Void>() {
@Override
public void onResult(final Void result, final Throwable t) {
done.set(true);
}
});
while (!done.get()) {
Thread.sleep(1);
}
db.getCollection(collectionName)
.insertOne(
new Document("foo", "bar"),
new SingleResultCallback<Void>() {
@Override
public void onResult(final Void result, final Throwable t) {
done.set(true);
}
});
while (!done.get()) {
Thread.sleep(1);
}
done.set(false);
db.getCollection(collectionName)
.count(
new SingleResultCallback<Long>() {
@Override
public void onResult(final Long result, final Throwable t) {
Assert.assertEquals(1, result.longValue());
done.set(true);
}
});
while (!done.get()) {
Thread.sleep(1);
}
// the final trace may still be reporting to the ListWriter,
// but we're only testing the first trace.
Assert.assertTrue(writer.size() >= 1);
final String createCollectionQuery =
"{ \"create\" : \"asyncCollection\", \"autoIndexId\" : \"?\", \"capped\" : \"?\" }";
final DDSpan trace0 = writer.get(0).get(0);
Assert.assertEquals("mongo.query", trace0.getOperationName());
Assert.assertEquals(createCollectionQuery, trace0.getResourceName());
Assert.assertEquals("mongodb", trace0.getType());
Assert.assertEquals("mongo", trace0.getServiceName());
Assert.assertEquals("java-mongo", trace0.getTags().get(Tags.COMPONENT.getKey()));
Assert.assertEquals(createCollectionQuery, trace0.getTags().get(Tags.DB_STATEMENT.getKey()));
Assert.assertEquals(MONGO_DB_NAME, trace0.getTags().get(Tags.DB_INSTANCE.getKey()));
Assert.assertEquals(MONGO_HOST, trace0.getTags().get(Tags.PEER_HOSTNAME.getKey()));
Assert.assertEquals("127.0.0.1", trace0.getTags().get(Tags.PEER_HOST_IPV4.getKey()));
Assert.assertEquals(MONGO_PORT, trace0.getTags().get(Tags.PEER_PORT.getKey()));
Assert.assertEquals("mongo", trace0.getTags().get(Tags.DB_TYPE.getKey()));
}
}

View File

@ -1,117 +0,0 @@
package datadog.trace.agent.integration;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import datadog.opentracing.DDSpan;
import datadog.opentracing.DDTracer;
import datadog.trace.agent.test.IntegrationTestUtils;
import datadog.trace.common.writer.ListWriter;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodProcess;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.process.runtime.Network;
import io.opentracing.tag.Tags;
import java.util.concurrent.TimeoutException;
import org.bson.Document;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class MongoClientInstrumentationTest {
public static final String MONGO_DB_NAME = "embedded";
public static final String MONGO_HOST = "localhost";
public static final int MONGO_PORT = 12345;
private static MongodExecutable mongodExe;
private static MongodProcess mongod;
private static MongoClient client;
private static final ListWriter writer = new ListWriter();
private static final DDTracer tracer = new DDTracer(writer);
public static void startLocalMongo() throws Exception {
final MongodStarter starter = MongodStarter.getDefaultInstance();
final IMongodConfig mongodConfig =
new MongodConfigBuilder()
.version(Version.Main.PRODUCTION)
.net(new Net(MONGO_HOST, MONGO_PORT, Network.localhostIsIPv6()))
.build();
mongodExe = starter.prepare(mongodConfig);
mongod = mongodExe.start();
}
public static void stopLocalMongo() throws Exception {
if (null != mongod) {
mongod.stop();
mongod = null;
}
if (null != mongodExe) {
mongodExe.stop();
mongodExe = null;
}
}
@BeforeClass
public static void setup() throws Exception {
IntegrationTestUtils.registerOrReplaceGlobalTracer(tracer);
startLocalMongo();
// Embeded Mongo uses HttpUrlConnection to download things so we have to clear traces before
// going to tests
writer.clear();
client = new MongoClient(MONGO_HOST, MONGO_PORT);
}
@AfterClass
public static void destroy() throws Exception {
if (null != client) {
client.close();
client = null;
}
stopLocalMongo();
}
@Test
public void syncClientHasListener() {
Assert.assertEquals(1, client.getMongoClientOptions().getCommandListeners().size());
Assert.assertEquals(
"TracingCommandListener",
client.getMongoClientOptions().getCommandListeners().get(0).getClass().getSimpleName());
}
@Test
public void insertOperation() throws TimeoutException, InterruptedException {
final MongoDatabase db = client.getDatabase(MONGO_DB_NAME);
final String collectionName = "testCollection";
db.createCollection(collectionName);
final MongoCollection<Document> collection = db.getCollection(collectionName);
collection.insertOne(new Document("foo", "bar"));
Assert.assertEquals(1, collection.count());
Assert.assertEquals(3, writer.size());
final String createCollectionQuery =
"{ \"create\" : \"testCollection\", \"autoIndexId\" : \"?\", \"capped\" : \"?\" }";
final DDSpan trace0 = writer.get(0).get(0);
Assert.assertEquals("mongo.query", trace0.getOperationName());
Assert.assertEquals(createCollectionQuery, trace0.getResourceName());
Assert.assertEquals("mongodb", trace0.getType());
Assert.assertEquals("mongo", trace0.getServiceName());
Assert.assertEquals("java-mongo", trace0.getTags().get(Tags.COMPONENT.getKey()));
Assert.assertEquals(createCollectionQuery, trace0.getTags().get(Tags.DB_STATEMENT.getKey()));
Assert.assertEquals(MONGO_DB_NAME, trace0.getTags().get(Tags.DB_INSTANCE.getKey()));
Assert.assertEquals(MONGO_HOST, trace0.getTags().get(Tags.PEER_HOSTNAME.getKey()));
Assert.assertEquals("127.0.0.1", trace0.getTags().get(Tags.PEER_HOST_IPV4.getKey()));
Assert.assertEquals(MONGO_PORT, trace0.getTags().get(Tags.PEER_PORT.getKey()));
Assert.assertEquals("mongo", trace0.getTags().get(Tags.DB_TYPE.getKey()));
}
}

View File

@ -4,6 +4,8 @@ import datadog.opentracing.DDSpan
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import java.util.regex.Pattern
import static TagsAssert.assertTags
class SpanAssert {
@ -52,11 +54,21 @@ class SpanAssert {
checked.operationName = true
}
def resourceName(Pattern pattern) {
assert span.resourceName.matches(pattern)
checked.resourceName = true
}
def resourceName(String name) {
assert span.resourceName == name
checked.resourceName = true
}
def resourceName(Closure<Boolean> eval) {
assert eval(span.resourceName)
checked.resourceName = true
}
def resourceNameContains(String... resourceNameParts) {
assertSpanNameContains(span.resourceName, resourceNameParts)
checked.resourceName = true

View File

@ -16,6 +16,7 @@ import java.util.concurrent.ExecutionException
import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer
import static datadog.trace.agent.test.utils.ConfigUtils.withConfigOverride
import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace
import static org.junit.Assume.assumeTrue
@ -108,7 +109,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
assertTraces(2) {
server.distributedRequestTrace(it, 0, trace(1).last())
trace(1, size(2)) {
parentSpan(it, 0)
basicSpan(it, 0, "parent")
clientSpan(it, 1, span(0), method, false)
}
}
@ -150,7 +151,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
// only one trace (client).
assertTraces(1) {
trace(0, size(2)) {
parentSpan(it, 0)
basicSpan(it, 0, "parent")
clientSpan(it, 1, span(0), method, renameService)
}
}
@ -173,7 +174,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
// only one trace (client).
assertTraces(1) {
trace(0, size(3)) {
parentSpan(it, 0)
basicSpan(it, 0, "parent")
span(1) {
operationName "child"
childOf span(0)
@ -304,7 +305,7 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
and:
assertTraces(1) {
trace(0, 2) {
parentSpan(it, 0, thrownException)
basicSpan(it, 0, "parent", thrownException)
clientSpan(it, 1, span(0), method, false, false, uri, null, thrownException)
}
}
@ -313,22 +314,6 @@ abstract class HttpClientTest<T extends HttpClientDecorator> extends AgentTestRu
method = "GET"
}
void parentSpan(TraceAssert trace, int index, Throwable exception = null) {
trace.span(index) {
parent()
serviceName "unnamed-java-app"
operationName "parent"
resourceName "parent"
errored exception != null
tags {
defaultTags()
if (exception) {
errorTags(exception.class, exception.message)
}
}
}
}
// parent span must be cast otherwise it breaks debugging classloading (junit loads it early)
void clientSpan(TraceAssert trace, int index, Object parentSpan, String method = "GET", boolean renameService = false, boolean tagQueryString = false, URI uri = server.address.resolve("/success"), Integer status = 200, Throwable exception = null) {
trace.span(index) {

View File

@ -1,6 +1,7 @@
package datadog.trace.agent.test.utils
import datadog.trace.agent.decorator.BaseDecorator
import datadog.trace.agent.test.asserts.TraceAssert
import datadog.trace.context.TraceScope
import io.opentracing.Scope
import io.opentracing.util.GlobalTracer
@ -40,4 +41,20 @@ class TraceUtils {
scope.close()
}
}
static basicSpan(TraceAssert trace, int index, String spanName, Throwable exception = null) {
trace.span(index) {
parent()
serviceName "unnamed-java-app"
operationName spanName
resourceName spanName
errored exception != null
tags {
defaultTags()
if (exception) {
errorTags(exception.class, exception.message)
}
}
}
}
}

View File

@ -68,6 +68,7 @@ public class Config {
public static final String HTTP_SERVER_TAG_QUERY_STRING = "http.server.tag.query-string";
public static final String HTTP_CLIENT_TAG_QUERY_STRING = "http.client.tag.query-string";
public static final String HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN = "trace.http.client.split-by-domain";
public static final String DB_CLIENT_HOST_SPLIT_BY_INSTANCE = "trace.db.client.split-by-instance";
public static final String PARTIAL_FLUSH_MIN_SPANS = "trace.partial.flush.min.spans";
public static final String RUNTIME_CONTEXT_FIELD_INJECTION =
"trace.runtime.context.field.injection";
@ -112,6 +113,7 @@ public class Config {
private static final boolean DEFAULT_HTTP_SERVER_TAG_QUERY_STRING = false;
private static final boolean DEFAULT_HTTP_CLIENT_TAG_QUERY_STRING = false;
private static final boolean DEFAULT_HTTP_CLIENT_SPLIT_BY_DOMAIN = false;
private static final boolean DEFAULT_DB_CLIENT_HOST_SPLIT_BY_INSTANCE = false;
private static final int DEFAULT_PARTIAL_FLUSH_MIN_SPANS = 1000;
private static final String DEFAULT_PROPAGATION_STYLE_EXTRACT = PropagationStyle.DATADOG.name();
private static final String DEFAULT_PROPAGATION_STYLE_INJECT = PropagationStyle.DATADOG.name();
@ -165,6 +167,7 @@ public class Config {
@Getter private final boolean httpServerTagQueryString;
@Getter private final boolean httpClientTagQueryString;
@Getter private final boolean httpClientSplitByDomain;
@Getter private final boolean dbClientSplitByInstance;
@Getter private final Integer partialFlushMinSpans;
@Getter private final boolean runtimeContextFieldInjection;
@Getter private final Set<PropagationStyle> propagationStylesToExtract;
@ -248,6 +251,10 @@ public class Config {
getBooleanSettingFromEnvironment(
HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, DEFAULT_HTTP_CLIENT_SPLIT_BY_DOMAIN);
dbClientSplitByInstance =
getBooleanSettingFromEnvironment(
DB_CLIENT_HOST_SPLIT_BY_INSTANCE, DEFAULT_DB_CLIENT_HOST_SPLIT_BY_INSTANCE);
partialFlushMinSpans =
getIntegerSettingFromEnvironment(PARTIAL_FLUSH_MIN_SPANS, DEFAULT_PARTIAL_FLUSH_MIN_SPANS);
@ -352,6 +359,10 @@ public class Config {
getPropertyBooleanValue(
properties, HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, parent.httpClientSplitByDomain);
dbClientSplitByInstance =
getPropertyBooleanValue(
properties, DB_CLIENT_HOST_SPLIT_BY_INSTANCE, parent.dbClientSplitByInstance);
partialFlushMinSpans =
getPropertyIntegerValue(properties, PARTIAL_FLUSH_MIN_SPANS, parent.partialFlushMinSpans);

View File

@ -9,6 +9,7 @@ import static datadog.trace.api.Config.AGENT_HOST
import static datadog.trace.api.Config.AGENT_PORT_LEGACY
import static datadog.trace.api.Config.AGENT_UNIX_DOMAIN_SOCKET
import static datadog.trace.api.Config.CONFIGURATION_FILE
import static datadog.trace.api.Config.DB_CLIENT_HOST_SPLIT_BY_INSTANCE
import static datadog.trace.api.Config.DEFAULT_JMX_FETCH_STATSD_PORT
import static datadog.trace.api.Config.GLOBAL_TAGS
import static datadog.trace.api.Config.HEADER_TAGS
@ -80,6 +81,7 @@ class ConfigTest extends Specification {
config.httpServerErrorStatuses == (500..599).toSet()
config.httpClientErrorStatuses == (400..499).toSet()
config.httpClientSplitByDomain == false
config.dbClientSplitByInstance == false
config.partialFlushMinSpans == 1000
config.reportHostName == false
config.runtimeContextFieldInjection == true
@ -121,6 +123,7 @@ class ConfigTest extends Specification {
prop.setProperty(HTTP_SERVER_ERROR_STATUSES, "123-456,457,124-125,122")
prop.setProperty(HTTP_CLIENT_ERROR_STATUSES, "111")
prop.setProperty(HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "true")
prop.setProperty(DB_CLIENT_HOST_SPLIT_BY_INSTANCE, "true")
prop.setProperty(PARTIAL_FLUSH_MIN_SPANS, "15")
prop.setProperty(TRACE_REPORT_HOSTNAME, "true")
prop.setProperty(RUNTIME_CONTEXT_FIELD_INJECTION, "false")
@ -152,6 +155,7 @@ class ConfigTest extends Specification {
config.httpServerErrorStatuses == (122..457).toSet()
config.httpClientErrorStatuses == (111..111).toSet()
config.httpClientSplitByDomain == true
config.dbClientSplitByInstance == true
config.partialFlushMinSpans == 15
config.reportHostName == true
config.runtimeContextFieldInjection == false
@ -184,6 +188,7 @@ class ConfigTest extends Specification {
System.setProperty(PREFIX + HTTP_SERVER_ERROR_STATUSES, "123-456,457,124-125,122")
System.setProperty(PREFIX + HTTP_CLIENT_ERROR_STATUSES, "111")
System.setProperty(PREFIX + HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "true")
System.setProperty(PREFIX + DB_CLIENT_HOST_SPLIT_BY_INSTANCE, "true")
System.setProperty(PREFIX + PARTIAL_FLUSH_MIN_SPANS, "25")
System.setProperty(PREFIX + TRACE_REPORT_HOSTNAME, "true")
System.setProperty(PREFIX + RUNTIME_CONTEXT_FIELD_INJECTION, "false")
@ -215,6 +220,7 @@ class ConfigTest extends Specification {
config.httpServerErrorStatuses == (122..457).toSet()
config.httpClientErrorStatuses == (111..111).toSet()
config.httpClientSplitByDomain == true
config.dbClientSplitByInstance == true
config.partialFlushMinSpans == 25
config.reportHostName == true
config.runtimeContextFieldInjection == false
@ -288,6 +294,7 @@ class ConfigTest extends Specification {
System.setProperty(PREFIX + HTTP_SERVER_ERROR_STATUSES, "1111")
System.setProperty(PREFIX + HTTP_CLIENT_ERROR_STATUSES, "1:1")
System.setProperty(PREFIX + HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "invalid")
System.setProperty(PREFIX + DB_CLIENT_HOST_SPLIT_BY_INSTANCE, "invalid")
System.setProperty(PREFIX + PROPAGATION_STYLE_EXTRACT, "some garbage")
System.setProperty(PREFIX + PROPAGATION_STYLE_INJECT, " ")
@ -308,6 +315,7 @@ class ConfigTest extends Specification {
config.httpServerErrorStatuses == (500..599).toSet()
config.httpClientErrorStatuses == (400..499).toSet()
config.httpClientSplitByDomain == false
config.dbClientSplitByInstance == false
config.propagationStylesToExtract.toList() == [Config.PropagationStyle.DATADOG]
config.propagationStylesToInject.toList() == [Config.PropagationStyle.DATADOG]
}
@ -373,6 +381,7 @@ class ConfigTest extends Specification {
properties.setProperty(HTTP_SERVER_ERROR_STATUSES, "123-456,457,124-125,122")
properties.setProperty(HTTP_CLIENT_ERROR_STATUSES, "111")
properties.setProperty(HTTP_CLIENT_HOST_SPLIT_BY_DOMAIN, "true")
properties.setProperty(DB_CLIENT_HOST_SPLIT_BY_INSTANCE, "true")
properties.setProperty(PARTIAL_FLUSH_MIN_SPANS, "15")
properties.setProperty(PROPAGATION_STYLE_EXTRACT, "B3 Datadog")
properties.setProperty(PROPAGATION_STYLE_INJECT, "Datadog B3")
@ -401,6 +410,7 @@ class ConfigTest extends Specification {
config.httpServerErrorStatuses == (122..457).toSet()
config.httpClientErrorStatuses == (111..111).toSet()
config.httpClientSplitByDomain == true
config.dbClientSplitByInstance == true
config.partialFlushMinSpans == 15
config.propagationStylesToExtract.toList() == [Config.PropagationStyle.B3, Config.PropagationStyle.DATADOG]
config.propagationStylesToInject.toList() == [Config.PropagationStyle.DATADOG, Config.PropagationStyle.B3]

View File

@ -7,7 +7,7 @@ ext {
slf4j : "1.7.25",
guava : "20.0", // Last version to support Java 7
jackson : "2.9.8", // https://nvd.nist.gov/vuln/detail/CVE-2018-1000873
jackson : "2.9.9", // https://nvd.nist.gov/vuln/detail/CVE-2019-12086
spock : "1.3-groovy-$spockGroovyVer",
groovy : groovyVer,

View File

@ -66,8 +66,9 @@ include ':dd-java-agent:instrumentation:jsp-2.3'
include ':dd-java-agent:instrumentation:kafka-clients-0.11'
include ':dd-java-agent:instrumentation:kafka-streams-0.11'
include ':dd-java-agent:instrumentation:lettuce-5'
include ':dd-java-agent:instrumentation:mongo-3.1'
include ':dd-java-agent:instrumentation:mongo-async-3.3'
include ':dd-java-agent:instrumentation:mongo'
include ':dd-java-agent:instrumentation:mongo:driver-3.1'
include ':dd-java-agent:instrumentation:mongo:driver-async-3.3'
include ':dd-java-agent:instrumentation:netty-4.0'
include ':dd-java-agent:instrumentation:netty-4.1'
include ':dd-java-agent:instrumentation:okhttp-3'