Merge branch 'master' of github.com:DataDog/dd-trace-java into labbati/config-file
This commit is contained in:
commit
a3be0cbf9f
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"() {
|
||||
|
|
|
@ -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 * _
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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", "?")
|
||||
}
|
||||
}
|
|
@ -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", "?"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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: '+'
|
|
@ -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) {
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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: '+'
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue