Merge pull request #141 from DataDog/tyler/jdbc

Add automatic instrumentation for JDBC
This commit is contained in:
Tyler Benson 2017-10-30 10:25:45 -07:00 committed by GitHub
commit 161d77bfa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 685 additions and 31 deletions

View File

@ -62,7 +62,7 @@ Finally, add the following JVM argument when starting your application—in your
-javaagent:/path/to/the/dd-java-agent.jar
```
The Java Agent—once passed to your application—automatically traces requests to the frameworks, application servers, and databases shown below. It does this by using various libraries from [opentracing-contrib](https://github.com/opentracing-contrib). In most cases you don't need to install or configure anything; traces will automatically show up in your Datadog dashboards. The exception is [any database library that uses JDBC](#jdbc).
The Java Agent—once passed to your application—automatically traces requests to the frameworks, application servers, and databases shown below. It does this by using various libraries from [opentracing-contrib](https://github.com/opentracing-contrib). In most cases you don't need to install or configure anything; traces will automatically show up in your Datadog dashboards.
#### Application Servers
@ -87,8 +87,7 @@ Also, frameworks like Spring Boot and Dropwizard inherently work because they us
| Database | Versions | Comments |
| ------------- |:-------------:| ----- |
| Spring JDBC| 4.x | **NOT traced automatically**—see [JDBC instructions](#jdbc) |
| Hibernate | 5.x | **NOT traced automatically**—see [JDBC instructions](#jdbc) |
| JDBC | 4.x | Intercepts calls to JDBC compatible clients |
| [MongoDB](https://github.com/opentracing-contrib/java-mongo-driver) | 3.x | Intercepts all the calls from the MongoDB client |
| [Cassandra](https://github.com/opentracing-contrib/java-cassandra-driver) | 3.2.x | Intercepts all the calls from the Cassandra client |
@ -103,22 +102,6 @@ disabledInstrumentations: ["opentracing-apache-httpclient", "opentracing-mongo-d
See [this YAML file](dd-java-agent/src/main/resources/dd-trace-supported-framework.yaml) for the proper names of all supported libraries (i.e. the names as you must list them in `disabledInstrumentations`).
#### JDBC
The Java Agent doesn't automatically trace requests to databases whose drivers are JDBC-based. For such databases, you must:
1. Add the opentracing-jdbc dependency to your project, e.g. for Maven, add this to pom.xml:
```
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-jdbc</artifactId>
<version>0.0.3</version>
</dependency>
```
2. Modify your code's database connection strings, e.g. for a connection string `jdbc:h2:mem:test`, make it `jdbc:tracing:h2:mem:test`.
### The `@Trace` Annotation
The Java Agent lets you add a `@Trace` annotation to any method to measure its execution time. Setup the [Java Agent](#java-agent-setup) first if you haven't done so.

View File

@ -28,6 +28,11 @@ dependencies {
testCompile group: 'javax.jms', name: 'javax.jms-api', version: '2.0.1'
testCompile group: 'org.apache.activemq.tooling', name: 'activemq-junit', version: '5.14.5'
testCompile group: 'org.apache.activemq', name: 'activemq-broker', version: '5.14.5'
// JDBC tests:
testCompile group: 'com.h2database', name: 'h2', version: '1.4.196'
testCompile group: 'org.hsqldb', name: 'hsqldb', version: '2.4.0'
testCompile group: 'org.apache.derby', name: 'derby', version: '10.14.1.0'
}
configurations.all {

View File

@ -0,0 +1,283 @@
package com.datadoghq.agent.integration.jdbc
import com.datadoghq.trace.DDTracer
import com.datadoghq.trace.writer.ListWriter
import io.opentracing.util.GlobalTracer
import org.apache.derby.jdbc.EmbeddedDriver
import org.h2.Driver
import org.hsqldb.jdbc.JDBCDriver
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import java.lang.reflect.Field
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.Statement
class JDBCInstrumentationTest extends Specification {
ListWriter writer = new ListWriter()
DDTracer tracer = new DDTracer(writer)
@Shared
private Map<String, Connection> connections
def setupSpec() {
Connection h2Connection = new Driver().connect("jdbc:h2:mem:integ-test", null)
Connection hsqlConnection = new JDBCDriver().connect("jdbc:hsqldb:mem:integTest", null)
Connection derbyConnection = new EmbeddedDriver().connect("jdbc:derby:memory:integTest;create=true", null)
connections = [
h2 : h2Connection,
derby : derbyConnection,
hsqldb: hsqlConnection,
]
}
def cleanupSpec() {
connections.values().each {
it.close()
}
}
def setup() {
try {
GlobalTracer.register(tracer)
} catch (final Exception e) {
// Force it anyway using reflection
final Field field = GlobalTracer.getDeclaredField("tracer")
field.setAccessible(true)
field.set(null, tracer)
}
writer.start()
assert GlobalTracer.isRegistered()
}
@Unroll
def "basic statement on #driver generates spans"() {
setup:
Statement statement = connection.createStatement()
ResultSet resultSet = statement.executeQuery(query)
expect:
resultSet.next()
resultSet.getInt(1) == 3
writer.size() == 1
def trace = writer.firstTrace()
trace.size() == 1
def span = trace[0]
span.context().operationName == "${driver}.query"
span.serviceName == driver
span.resourceName == query
span.type == "sql"
!span.context().getErrorFlag()
span.context().parentId == 0
def tags = span.context().tags
tags["db.type"] == driver
tags["span.kind"] == "client"
tags["component"] == "java-jdbc-statement"
tags["db.jdbc.url"].contains(driver)
tags["span.origin.type"] != null
tags["thread.name"] != null
tags["thread.id"] != null
tags.size() == 7
cleanup:
statement.close()
where:
driver | connection | query
"h2" | connections.get("h2") | "SELECT 3"
"derby" | connections.get("derby") | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | connections.get("hsqldb") | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
}
@Unroll
def "prepared statement execute on #driver generates a span"() {
setup:
PreparedStatement statement = connection.prepareStatement(query)
assert statement.execute()
ResultSet resultSet = statement.resultSet
expect:
resultSet.next()
resultSet.getInt(1) == 3
writer.size() == 1
def trace = writer.firstTrace()
trace.size() == 1
def span = trace[0]
span.context().operationName == "${driver}.query"
span.serviceName == driver
span.resourceName == query
span.type == "sql"
!span.context().getErrorFlag()
span.context().parentId == 0
def tags = span.context().tags
tags["db.type"] == driver
tags["span.kind"] == "client"
tags["component"] == "java-jdbc-prepared_statement"
tags["db.jdbc.url"].contains(driver)
tags["span.origin.type"] != null
tags["thread.name"] != null
tags["thread.id"] != null
tags.size() == 7
cleanup:
statement.close()
where:
driver | connection | query
"h2" | connections.get("h2") | "SELECT 3"
"derby" | connections.get("derby") | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | connections.get("hsqldb") | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
}
@Unroll
def "prepared statement query on #driver generates a span"() {
setup:
PreparedStatement statement = connection.prepareStatement(query)
ResultSet resultSet = statement.executeQuery()
expect:
resultSet.next()
resultSet.getInt(1) == 3
writer.size() == 1
def trace = writer.firstTrace()
trace.size() == 1
def span = trace[0]
span.context().operationName == "${driver}.query"
span.serviceName == driver
span.resourceName == query
span.type == "sql"
!span.context().getErrorFlag()
span.context().parentId == 0
def tags = span.context().tags
tags["db.type"] == driver
tags["span.kind"] == "client"
tags["component"] == "java-jdbc-prepared_statement"
tags["db.jdbc.url"].contains(driver)
tags["span.origin.type"] != null
tags["thread.name"] != null
tags["thread.id"] != null
tags.size() == 7
cleanup:
statement.close()
where:
driver | connection | query
"h2" | connections.get("h2") | "SELECT 3"
"derby" | connections.get("derby") | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
"hsqldb" | connections.get("hsqldb") | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
}
@Unroll
def "statement update on #driver generates a span"() {
setup:
Statement statement = connection.createStatement()
def sql = connection.nativeSQL(query)
expect:
!statement.execute(sql)
statement.updateCount == 0
writer.size() == 1
def trace = writer.firstTrace()
trace.size() == 1
def span = trace[0]
span.context().operationName == "${driver}.query"
span.serviceName == driver
span.resourceName == query
span.type == "sql"
!span.context().getErrorFlag()
span.context().parentId == 0
def tags = span.context().tags
tags["db.type"] == driver
tags["span.kind"] == "client"
tags["component"] == "java-jdbc-statement"
tags["db.jdbc.url"].contains(driver)
tags["span.origin.type"] != null
tags["thread.name"] != null
tags["thread.id"] != null
tags.size() == 7
cleanup:
statement.close()
where:
driver | connection | query
"h2" | connections.get("h2") | "CREATE TABLE S_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))"
"derby" | connections.get("derby") | "CREATE TABLE S_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))"
"hsqldb" | connections.get("hsqldb") | "CREATE TABLE PUBLIC.S_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))"
}
@Unroll
def "prepared statement update on #driver generates a span"() {
setup:
def sql = connection.nativeSQL(query)
PreparedStatement statement = connection.prepareStatement(sql)
expect:
statement.executeUpdate() == 0
writer.size() == 1
def trace = writer.firstTrace()
trace.size() == 1
def span = trace[0]
span.context().operationName == "${driver}.query"
span.serviceName == driver
span.resourceName == query
span.type == "sql"
!span.context().getErrorFlag()
span.context().parentId == 0
def tags = span.context().tags
tags["db.type"] == driver
tags["span.kind"] == "client"
tags["component"] == "java-jdbc-prepared_statement"
tags["db.jdbc.url"].contains(driver)
tags["span.origin.type"] != null
tags["thread.name"] != null
tags["thread.id"] != null
tags.size() == 7
cleanup:
statement.close()
where:
driver | connection | query
"h2" | connections.get("h2") | "CREATE TABLE PS_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))"
// Derby calls executeLargeUpdate from executeUpdate thus generating a nested span breaking this test.
"hsqldb" | connections.get("hsqldb") | "CREATE TABLE PUBLIC.PS_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))"
}
}

View File

@ -20,6 +20,7 @@ dependencies {
compile project(':dd-trace')
compile project(':dd-trace-annotations')
compile group: 'net.bytebuddy', name: 'byte-buddy', version: '1.7.6'
compile group: 'org.jboss.byteman', name: 'byteman', version: '3.0.10'
compile group: 'org.reflections', name: 'reflections', version: '0.9.11'
@ -36,9 +37,6 @@ dependencies {
testCompile(project(path: ':dd-java-agent:integrations:helpers')) {
transitive = false
}
// Not bundled in with the agent. Usage requires being on the app's classpath (eg. Spring Boot's executable jar)
compileOnly group: 'io.opentracing.contrib', name: 'opentracing-jdbc', version: '0.0.3'
}
project(':dd-java-agent:integrations:helpers').afterEvaluate { helperProject ->
@ -90,6 +88,7 @@ shadowJar {
relocate 'com.fasterxml', 'dd.deps.com.fasterxml'
relocate 'javassist', 'dd.deps.javassist'
relocate 'net.bytebuddy', 'dd.deps.net.bytebuddy'
relocate 'org.reflections', 'dd.deps.org.reflections'
relocate('org.jboss.byteman', 'dd.deps.org.jboss.byteman') {
// Renaming these causes a verify error in the tests.

View File

@ -16,8 +16,21 @@
*/
package com.datadoghq.agent;
import static com.datadoghq.agent.utils.ClassLoaderNameMatcher.classLoaderWithName;
import static com.datadoghq.agent.utils.ClassLoaderNameMatcher.isReflectionClassLoader;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import com.datadoghq.agent.instrumentation.Instrumenter;
import java.lang.instrument.Instrumentation;
import java.util.ServiceLoader;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.utility.JavaModule;
/**
* This class provides a wrapper around the ByteMan agent, to establish required system properties
@ -27,12 +40,14 @@ import lombok.extern.slf4j.Slf4j;
public class TracingAgent {
public static void premain(String agentArgs, final Instrumentation inst) throws Exception {
addByteBuddy(inst);
agentArgs = addManager(agentArgs);
log.debug("Using premain for loading {}", TracingAgent.class.getSimpleName());
org.jboss.byteman.agent.Main.premain(agentArgs, inst);
}
public static void agentmain(String agentArgs, final Instrumentation inst) throws Exception {
addByteBuddy(inst);
agentArgs = addManager(agentArgs);
log.debug("Using agentmain for loading {}", TracingAgent.class.getSimpleName());
org.jboss.byteman.agent.Main.agentmain(agentArgs, inst);
@ -48,4 +63,81 @@ public class TracingAgent {
log.debug("Agent args=: {}", agentArgs);
return agentArgs;
}
public static void addByteBuddy(final Instrumentation inst) {
AgentBuilder agentBuilder =
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(new Listener())
.ignore(nameStartsWith("com.datadoghq.agent.integration"))
.or(nameStartsWith("java."))
.or(nameStartsWith("com.sun."))
.or(nameStartsWith("sun."))
.or(nameStartsWith("jdk."))
.or(nameStartsWith("org.aspectj."))
.or(nameStartsWith("org.groovy."))
.or(nameStartsWith("com.p6spy."))
.or(nameStartsWith("org.slf4j."))
.or(nameContains("javassist"))
.or(nameContains(".asm."))
.ignore(
any(),
isBootstrapClassLoader()
.or(isReflectionClassLoader())
.or(
classLoaderWithName(
"org.codehaus.groovy.runtime.callsite.CallSiteClassLoader")));
for (final Instrumenter instrumenter : ServiceLoader.load(Instrumenter.class)) {
agentBuilder = instrumenter.instrument(agentBuilder);
}
agentBuilder.installOn(inst);
}
@Slf4j
static class Listener implements AgentBuilder.Listener {
@Override
public void onError(
final String typeName,
final ClassLoader classLoader,
final JavaModule module,
final boolean loaded,
final Throwable throwable) {
log.warn("Failed to handle " + typeName + " for transformation", throwable);
}
@Override
public void onTransformation(
final TypeDescription typeDescription,
final ClassLoader classLoader,
final JavaModule module,
final boolean loaded,
final DynamicType dynamicType) {
log.debug("Transformed {}", typeDescription);
}
@Override
public void onIgnored(
final TypeDescription typeDescription,
final ClassLoader classLoader,
final JavaModule module,
final boolean loaded) {}
@Override
public void onComplete(
final String typeName,
final ClassLoader classLoader,
final JavaModule module,
final boolean loaded) {}
@Override
public void onDiscovery(
final String typeName,
final ClassLoader classLoader,
final JavaModule module,
final boolean loaded) {}
}
}

View File

@ -0,0 +1,7 @@
package com.datadoghq.agent.instrumentation;
import net.bytebuddy.agent.builder.AgentBuilder;
public interface Instrumenter {
AgentBuilder instrument(AgentBuilder agentBuilder);
}

View File

@ -0,0 +1,44 @@
package com.datadoghq.agent.instrumentation.jdbc;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
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.datadoghq.agent.instrumentation.Instrumenter;
import com.google.auto.service.AutoService;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.WeakHashMap;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
@AutoService(Instrumenter.class)
public final class ConnectionInstrumentation implements Instrumenter {
public static final Map<PreparedStatement, String> preparedStatements = new WeakHashMap<>();
@Override
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
return agentBuilder
.type(not(isInterface()).and(hasSuperType(named(Connection.class.getName()))))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
nameStartsWith("prepare")
.and(takesArgument(0, String.class))
.and(returns(PreparedStatement.class)),
ConnectionAdvice.class.getName()));
}
public static class ConnectionAdvice {
@Advice.OnMethodExit
public static void addDBInfo(
@Advice.Argument(0) final String sql, @Advice.Return final PreparedStatement statement) {
preparedStatements.put(statement, sql);
}
}
}

View File

@ -0,0 +1,55 @@
package com.datadoghq.agent.instrumentation.jdbc;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.datadoghq.agent.instrumentation.Instrumenter;
import com.google.auto.service.AutoService;
import java.sql.Connection;
import java.sql.Driver;
import java.util.Map;
import java.util.Properties;
import java.util.WeakHashMap;
import lombok.Data;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
@AutoService(Instrumenter.class)
public final class DriverInstrumentation implements Instrumenter {
public static final Map<Connection, DBInfo> connectionInfo = new WeakHashMap<>();
@Override
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
return agentBuilder
.type(not(isInterface()).and(hasSuperType(named(Driver.class.getName()))))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
named("connect").and(takesArguments(String.class, Properties.class)),
DriverAdvice.class.getName()));
}
public static class DriverAdvice {
@Advice.OnMethodExit
public static void addDBInfo(
@Advice.Argument(0) final String url,
@Advice.Argument(1) final Properties info,
@Advice.Return final Connection connection) {
// Remove end of url to prevent passwords from leaking:
final String sanitizedURL = url.replaceAll("[?;].*", "");
final String type = url.split(":")[1];
final String dbUser = info == null ? null : info.getProperty("user");
connectionInfo.put(connection, new DBInfo(sanitizedURL, type, dbUser));
}
}
@Data
public static class DBInfo {
private final String url;
private final String type;
private final String user;
}
}

View File

@ -0,0 +1,83 @@
package com.datadoghq.agent.instrumentation.jdbc;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
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.takesArguments;
import com.datadoghq.agent.instrumentation.Instrumenter;
import com.datadoghq.trace.DDTags;
import com.google.auto.service.AutoService;
import io.opentracing.ActiveSpan;
import io.opentracing.NoopActiveSpanSource;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Collections;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
@AutoService(Instrumenter.class)
public final class PreparedStatementInstrumentation implements Instrumenter {
@Override
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
return agentBuilder
.type(not(isInterface()).and(hasSuperType(named(PreparedStatement.class.getName()))))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
nameStartsWith("execute").and(takesArguments(0)).and(isPublic()),
PreparedStatementAdvice.class.getName()))
.asDecorator();
}
public static class PreparedStatementAdvice {
@Advice.OnMethodEnter
public static ActiveSpan startSpan(@Advice.This final PreparedStatement statement) {
final String sql = ConnectionInstrumentation.preparedStatements.get(statement);
final Connection connection;
try {
connection = statement.getConnection();
} catch (final Throwable e) {
// Had some problem getting the connection.
return NoopActiveSpanSource.NoopActiveSpan.INSTANCE;
}
final DriverInstrumentation.DBInfo dbInfo =
DriverInstrumentation.connectionInfo.get(connection);
final ActiveSpan span =
GlobalTracer.get().buildSpan(dbInfo.getType() + ".query").startActive();
Tags.DB_TYPE.set(span, dbInfo.getType());
Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT);
Tags.COMPONENT.set(span, "java-jdbc-prepared_statement");
span.setTag(DDTags.SERVICE_NAME, dbInfo.getType());
span.setTag(DDTags.RESOURCE_NAME, sql);
span.setTag(DDTags.SPAN_TYPE, "sql");
span.setTag("span.origin.type", statement.getClass().getName());
span.setTag("db.jdbc.url", dbInfo.getUrl());
if (dbInfo.getUser() != null) {
Tags.DB_USER.set(span, dbInfo.getUser());
}
return span;
}
@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void stopSpan(
@Advice.Enter final ActiveSpan activeSpan, @Advice.Thrown final Throwable throwable) {
if (throwable != null) {
Tags.ERROR.set(activeSpan, true);
activeSpan.log(Collections.singletonMap("error.object", throwable));
}
activeSpan.deactivate();
}
}
}

View File

@ -0,0 +1,83 @@
package com.datadoghq.agent.instrumentation.jdbc;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
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.takesArgument;
import com.datadoghq.agent.instrumentation.Instrumenter;
import com.datadoghq.trace.DDTags;
import com.google.auto.service.AutoService;
import io.opentracing.ActiveSpan;
import io.opentracing.NoopActiveSpanSource;
import io.opentracing.tag.Tags;
import io.opentracing.util.GlobalTracer;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Collections;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
@AutoService(Instrumenter.class)
public final class StatementInstrumentation implements Instrumenter {
@Override
public AgentBuilder instrument(final AgentBuilder agentBuilder) {
return agentBuilder
.type(not(isInterface()).and(hasSuperType(named(Statement.class.getName()))))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
nameStartsWith("execute").and(takesArgument(0, String.class)).and(isPublic()),
StatementAdvice.class.getName()))
.asDecorator();
}
public static class StatementAdvice {
@Advice.OnMethodEnter
public static ActiveSpan startSpan(
@Advice.Argument(0) final String sql, @Advice.This final Statement statement) {
final Connection connection;
try {
connection = statement.getConnection();
} catch (final Throwable e) {
// Had some problem getting the connection.
return NoopActiveSpanSource.NoopActiveSpan.INSTANCE;
}
final DriverInstrumentation.DBInfo dbInfo =
DriverInstrumentation.connectionInfo.get(connection);
final ActiveSpan span =
GlobalTracer.get().buildSpan(dbInfo.getType() + ".query").startActive();
Tags.DB_TYPE.set(span, dbInfo.getType());
Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT);
Tags.COMPONENT.set(span, "java-jdbc-statement");
span.setTag(DDTags.SERVICE_NAME, dbInfo.getType());
span.setTag(DDTags.RESOURCE_NAME, sql);
span.setTag(DDTags.SPAN_TYPE, "sql");
span.setTag("span.origin.type", statement.getClass().getName());
span.setTag("db.jdbc.url", dbInfo.getUrl());
if (dbInfo.getUser() != null) {
Tags.DB_USER.set(span, dbInfo.getUser());
}
return span;
}
@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void stopSpan(
@Advice.Enter final ActiveSpan activeSpan, @Advice.Thrown final Throwable throwable) {
if (throwable != null) {
Tags.ERROR.set(activeSpan, true);
activeSpan.log(Collections.singletonMap("error.object", throwable));
}
activeSpan.deactivate();
}
}
}

View File

@ -0,0 +1,27 @@
package com.datadoghq.agent.utils;
import net.bytebuddy.matcher.ElementMatcher;
// Borrowed from https://github.com/stagemonitor/stagemonitor/blob/master/stagemonitor-core/src/main/java/org/stagemonitor/core/instrument/ClassLoaderNameMatcher.java
public class ClassLoaderNameMatcher extends ElementMatcher.Junction.AbstractBase<ClassLoader> {
private final String name;
private ClassLoaderNameMatcher(final String name) {
this.name = name;
}
public static ElementMatcher.Junction.AbstractBase<ClassLoader> classLoaderWithName(
final String name) {
return new ClassLoaderNameMatcher(name);
}
public static ElementMatcher.Junction.AbstractBase<ClassLoader> isReflectionClassLoader() {
return new ClassLoaderNameMatcher("sun.reflect.DelegatingClassLoader");
}
@Override
public boolean matches(final ClassLoader target) {
return target != null && name.equals(target.getClass().getName());
}
}

View File

@ -57,9 +57,3 @@ instruments:
- The JDBC driver
The Java Agent embeds the [OpenTracing Java Agent](https://github.com/opentracing-contrib/java-agent).
#### Note for JDBC tracing configuration
[JDBC is not automatically instrumented by the Java Agent](../../README.md#jdbc), so we changed the `application.properties`
[file](src/main/resources/application.properties) to use the OpenTracing Driver and included it as a dependency in `spring-boot-jdbc.gradle`.
Without these steps in your applications, the TracingDriver will not work.

View File

@ -11,7 +11,6 @@ description = 'spring-boot-jdbc'
dependencies {
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.3'
compile group: 'io.opentracing.contrib', name: 'opentracing-jdbc', version: '0.0.3'
compile group: 'com.h2database', name: 'h2', version: '1.4.196'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '1.5.4.RELEASE'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '1.5.4.RELEASE'

View File

@ -1,6 +1,6 @@
# you must set the following so that OpenTracing traced driver is used
spring.datasource.driver-class-name=io.opentracing.contrib.jdbc.TracingDriver
spring.datasource.url=jdbc:tracing:h2:mem:spring-test;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:spring-test;DB_CLOSE_ON_EXIT=FALSE
# set the logging level
logging.level.root=INFO