Migrate JDBC instrumentation to Decorator
This commit is contained in:
parent
0cdc80aa14
commit
c46909e59f
|
@ -3,13 +3,13 @@ package datadog.trace.agent.decorator;
|
||||||
import io.opentracing.Span;
|
import io.opentracing.Span;
|
||||||
import io.opentracing.tag.Tags;
|
import io.opentracing.tag.Tags;
|
||||||
|
|
||||||
public abstract class DatabaseClientDecorator<SESSION> extends ClientDecorator {
|
public abstract class DatabaseClientDecorator<CONNECTION> extends ClientDecorator {
|
||||||
|
|
||||||
protected abstract String dbType();
|
protected abstract String dbType();
|
||||||
|
|
||||||
protected abstract String dbUser(SESSION session);
|
protected abstract String dbUser(CONNECTION connection);
|
||||||
|
|
||||||
protected abstract String dbInstance(SESSION session);
|
protected abstract String dbInstance(CONNECTION connection);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Span afterStart(final Span span) {
|
public Span afterStart(final Span span) {
|
||||||
|
@ -18,11 +18,11 @@ public abstract class DatabaseClientDecorator<SESSION> extends ClientDecorator {
|
||||||
return super.afterStart(span);
|
return super.afterStart(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Span onSession(final Span span, final SESSION statement) {
|
public Span onConnection(final Span span, final CONNECTION connection) {
|
||||||
assert span != null;
|
assert span != null;
|
||||||
if (statement != null) {
|
if (connection != null) {
|
||||||
Tags.DB_USER.set(span, dbUser(statement));
|
Tags.DB_USER.set(span, dbUser(connection));
|
||||||
Tags.DB_INSTANCE.set(span, dbInstance(statement));
|
Tags.DB_INSTANCE.set(span, dbInstance(connection));
|
||||||
}
|
}
|
||||||
return span;
|
return span;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ class DatabaseClientDecoratorTest extends ClientDecoratorTest {
|
||||||
def decorator = newDecorator()
|
def decorator = newDecorator()
|
||||||
|
|
||||||
when:
|
when:
|
||||||
decorator.onSession(span, session)
|
decorator.onConnection(span, session)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
if (session) {
|
if (session) {
|
||||||
|
@ -81,7 +81,7 @@ class DatabaseClientDecoratorTest extends ClientDecoratorTest {
|
||||||
thrown(AssertionError)
|
thrown(AssertionError)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
decorator.onSession(null, null)
|
decorator.onConnection(null, null)
|
||||||
|
|
||||||
then:
|
then:
|
||||||
thrown(AssertionError)
|
thrown(AssertionError)
|
||||||
|
|
|
@ -212,7 +212,7 @@ public class TracingSession implements Session {
|
||||||
private Span buildSpan(final String query) {
|
private Span buildSpan(final String query) {
|
||||||
final Span span = tracer.buildSpan("cassandra.execute").start();
|
final Span span = tracer.buildSpan("cassandra.execute").start();
|
||||||
DECORATE.afterStart(span);
|
DECORATE.afterStart(span);
|
||||||
DECORATE.onSession(span, session);
|
DECORATE.onConnection(span, session);
|
||||||
DECORATE.onStatement(span, query);
|
DECORATE.onStatement(span, query);
|
||||||
return span;
|
return span;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class SlickTest extends AgentTestRunner {
|
||||||
"$Tags.DB_TYPE.key" SlickUtils.Driver()
|
"$Tags.DB_TYPE.key" SlickUtils.Driver()
|
||||||
"$Tags.DB_USER.key" SlickUtils.Username()
|
"$Tags.DB_USER.key" SlickUtils.Username()
|
||||||
|
|
||||||
"db.jdbc.url" SlickUtils.Url()
|
"db.instance" SlickUtils.Url()
|
||||||
"span.origin.type" "org.h2.jdbc.JdbcPreparedStatement"
|
"span.origin.type" "org.h2.jdbc.JdbcPreparedStatement"
|
||||||
|
|
||||||
defaultTags()
|
defaultTags()
|
||||||
|
|
|
@ -11,7 +11,6 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
import datadog.trace.agent.tooling.Instrumenter;
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
import datadog.trace.bootstrap.JDBCMaps;
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
|
@ -31,6 +30,13 @@ public final class ConnectionInstrumentation extends Instrumenter.Default {
|
||||||
return not(isInterface()).and(safeHasSuperType(named("java.sql.Connection")));
|
return not(isInterface()).and(safeHasSuperType(named("java.sql.Connection")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] helperClassNames() {
|
||||||
|
return new String[] {
|
||||||
|
packageName + ".JDBCMaps",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
return singletonMap(
|
return singletonMap(
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package datadog.trace.instrumentation.jdbc;
|
||||||
|
|
||||||
|
import datadog.trace.agent.decorator.DatabaseClientDecorator;
|
||||||
|
import datadog.trace.api.DDSpanTypes;
|
||||||
|
import datadog.trace.api.DDTags;
|
||||||
|
import io.opentracing.Span;
|
||||||
|
import io.opentracing.tag.Tags;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
|
||||||
|
public static final JDBCDecorator DECORATE = new JDBCDecorator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] instrumentationNames() {
|
||||||
|
return new String[] {"jdbc"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String service() {
|
||||||
|
return "jdbc"; // Overridden by onConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String component() {
|
||||||
|
return "java-jdbc"; // Overridden by onStatement and onPreparedStatement
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String spanType() {
|
||||||
|
return DDSpanTypes.SQL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String dbType() {
|
||||||
|
return "jdbc";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String dbUser(final JDBCMaps.DBInfo info) {
|
||||||
|
return info.getUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String dbInstance(final JDBCMaps.DBInfo info) {
|
||||||
|
return info.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Span onConnection(final Span span, final Connection connection) {
|
||||||
|
JDBCMaps.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.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
if (dbInfo == null) {
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
dbInfo = JDBCMaps.DBInfo.DEFAULT;
|
||||||
|
}
|
||||||
|
} catch (final SQLException se) {
|
||||||
|
dbInfo = JDBCMaps.DBInfo.DEFAULT;
|
||||||
|
}
|
||||||
|
JDBCMaps.connectionInfo.put(connection, dbInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbInfo != null) {
|
||||||
|
Tags.DB_TYPE.set(span, dbInfo.getType());
|
||||||
|
span.setTag(DDTags.SERVICE_NAME, dbInfo.getType());
|
||||||
|
}
|
||||||
|
return super.onConnection(span, dbInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Span onStatement(final Span span, final String statement) {
|
||||||
|
final String resourceName = statement == null ? JDBCMaps.DB_QUERY : statement;
|
||||||
|
span.setTag(DDTags.RESOURCE_NAME, resourceName);
|
||||||
|
Tags.COMPONENT.set(span, "java-jdbc-statement");
|
||||||
|
return super.onStatement(span, statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
span.setTag(DDTags.RESOURCE_NAME, resourceName);
|
||||||
|
Tags.COMPONENT.set(span, "java-jdbc-prepared_statement");
|
||||||
|
return super.onStatement(span, sql);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
package datadog.trace.bootstrap;
|
package datadog.trace.instrumentation.jdbc;
|
||||||
|
|
||||||
import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
|
import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.WeakMap;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -9,7 +10,7 @@ import lombok.Data;
|
||||||
/**
|
/**
|
||||||
* JDBC instrumentation shares a global map of connection info.
|
* JDBC instrumentation shares a global map of connection info.
|
||||||
*
|
*
|
||||||
* <p>In the bootstrap project to ensure visibility by all classes.
|
* <p>Should be injected into the bootstrap classpath.
|
||||||
*/
|
*/
|
||||||
public class JDBCMaps {
|
public class JDBCMaps {
|
||||||
public static final WeakMap<Connection, DBInfo> connectionInfo = newWeakMap();
|
public static final WeakMap<Connection, DBInfo> connectionInfo = newWeakMap();
|
|
@ -0,0 +1,36 @@
|
||||||
|
package datadog.trace.instrumentation.jdbc;
|
||||||
|
|
||||||
|
import datadog.trace.bootstrap.ExceptionLogger;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
public abstract class JDBCUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param statement
|
||||||
|
* @return the unwrapped connection or null if exception was thrown.
|
||||||
|
*/
|
||||||
|
public static Connection connectionFromStatement(final Statement statement) {
|
||||||
|
Connection connection;
|
||||||
|
try {
|
||||||
|
connection = statement.getConnection();
|
||||||
|
try {
|
||||||
|
// unwrap the connection to cache the underlying actual connection and to not cache proxy
|
||||||
|
// objects
|
||||||
|
if (connection.isWrapperFor(Connection.class)) {
|
||||||
|
connection = connection.unwrap(Connection.class);
|
||||||
|
}
|
||||||
|
} catch (final Exception | AbstractMethodError e) {
|
||||||
|
// 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)
|
||||||
|
// Stick with original connection.
|
||||||
|
}
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
// Had some problem getting the connection.
|
||||||
|
ExceptionLogger.LOGGER.debug("Could not get connection for StatementAdvice", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
package datadog.trace.instrumentation.jdbc;
|
package datadog.trace.instrumentation.jdbc;
|
||||||
|
|
||||||
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DECORATE;
|
||||||
|
import static datadog.trace.instrumentation.jdbc.JDBCUtils.connectionFromStatement;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
|
@ -12,21 +13,13 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||||
|
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
import datadog.trace.agent.tooling.Instrumenter;
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
import datadog.trace.api.DDSpanTypes;
|
|
||||||
import datadog.trace.api.DDTags;
|
|
||||||
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
|
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
|
||||||
import datadog.trace.bootstrap.ExceptionLogger;
|
|
||||||
import datadog.trace.bootstrap.JDBCMaps;
|
|
||||||
import io.opentracing.Scope;
|
import io.opentracing.Scope;
|
||||||
import io.opentracing.Span;
|
import io.opentracing.Span;
|
||||||
import io.opentracing.noop.NoopScopeManager.NoopScope;
|
import io.opentracing.noop.NoopScopeManager;
|
||||||
import io.opentracing.tag.Tags;
|
|
||||||
import io.opentracing.util.GlobalTracer;
|
import io.opentracing.util.GlobalTracer;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DatabaseMetaData;
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
import net.bytebuddy.description.method.MethodDescription;
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
@ -35,6 +28,7 @@ import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
@AutoService(Instrumenter.class)
|
@AutoService(Instrumenter.class)
|
||||||
public final class PreparedStatementInstrumentation extends Instrumenter.Default {
|
public final class PreparedStatementInstrumentation extends Instrumenter.Default {
|
||||||
|
|
||||||
public PreparedStatementInstrumentation() {
|
public PreparedStatementInstrumentation() {
|
||||||
super("jdbc");
|
super("jdbc");
|
||||||
}
|
}
|
||||||
|
@ -44,6 +38,19 @@ public final class PreparedStatementInstrumentation extends Instrumenter.Default
|
||||||
return not(isInterface()).and(safeHasSuperType(named("java.sql.PreparedStatement")));
|
return not(isInterface()).and(safeHasSuperType(named("java.sql.PreparedStatement")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
return singletonMap(
|
return singletonMap(
|
||||||
|
@ -59,80 +66,18 @@ public final class PreparedStatementInstrumentation extends Instrumenter.Default
|
||||||
if (callDepth > 0) {
|
if (callDepth > 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final String sql = JDBCMaps.preparedStatements.get(statement);
|
|
||||||
Connection connection;
|
final Connection connection = connectionFromStatement(statement);
|
||||||
try {
|
if (connection == null) {
|
||||||
connection = statement.getConnection();
|
return NoopScopeManager.NoopScope.INSTANCE;
|
||||||
try {
|
|
||||||
// unwrap the connection to cache the underlying actual connection and to not cache proxy
|
|
||||||
// objects
|
|
||||||
if (connection.isWrapperFor(Connection.class)) {
|
|
||||||
connection = connection.unwrap(Connection.class);
|
|
||||||
}
|
|
||||||
} catch (final Exception | AbstractMethodError e) {
|
|
||||||
// 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)
|
|
||||||
// Stick with original connection.
|
|
||||||
}
|
|
||||||
} catch (final Throwable e) {
|
|
||||||
// Had some problem getting the connection.
|
|
||||||
ExceptionLogger.LOGGER.debug("Could not get connection for PreparedStatementAdvice", e);
|
|
||||||
return NoopScope.INSTANCE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JDBCMaps.DBInfo dbInfo = JDBCMaps.connectionInfo.get(connection);
|
final Scope scope = GlobalTracer.get().buildSpan("database.query").startActive(true);
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* <p>This should be a util method to be shared between PreparedStatementInstrumentation and
|
|
||||||
* StatementInstrumentation class, but java.sql.* are on the platform classloaders in Java 9,
|
|
||||||
* which prevents us from referencing them in the bootstrap utils.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
if (dbInfo == null) {
|
|
||||||
try {
|
|
||||||
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);
|
|
||||||
} else {
|
|
||||||
dbInfo = JDBCMaps.DBInfo.DEFAULT;
|
|
||||||
}
|
|
||||||
} catch (final SQLException se) {
|
|
||||||
dbInfo = JDBCMaps.DBInfo.DEFAULT;
|
|
||||||
}
|
|
||||||
JDBCMaps.connectionInfo.put(connection, dbInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Scope scope =
|
|
||||||
GlobalTracer.get().buildSpan(dbInfo.getType() + ".query").startActive(true);
|
|
||||||
|
|
||||||
final Span span = scope.span();
|
final Span span = scope.span();
|
||||||
Tags.DB_TYPE.set(span, dbInfo.getType());
|
DECORATE.afterStart(span);
|
||||||
Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT);
|
DECORATE.onConnection(span, connection);
|
||||||
Tags.COMPONENT.set(span, "java-jdbc-prepared_statement");
|
DECORATE.onPreparedStatement(span, statement);
|
||||||
|
|
||||||
span.setTag(DDTags.SERVICE_NAME, dbInfo.getType());
|
|
||||||
span.setTag(DDTags.RESOURCE_NAME, sql == null ? JDBCMaps.DB_QUERY : sql);
|
|
||||||
span.setTag(DDTags.SPAN_TYPE, DDSpanTypes.SQL);
|
|
||||||
span.setTag("span.origin.type", statement.getClass().getName());
|
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 scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,11 +85,8 @@ public final class PreparedStatementInstrumentation extends Instrumenter.Default
|
||||||
public static void stopSpan(
|
public static void stopSpan(
|
||||||
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
|
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
|
||||||
if (scope != null) {
|
if (scope != null) {
|
||||||
if (throwable != null) {
|
DECORATE.onError(scope.span(), throwable);
|
||||||
final Span span = scope.span();
|
DECORATE.beforeFinish(scope.span());
|
||||||
Tags.ERROR.set(span, true);
|
|
||||||
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
|
|
||||||
}
|
|
||||||
scope.close();
|
scope.close();
|
||||||
CallDepthThreadLocalMap.reset(PreparedStatement.class);
|
CallDepthThreadLocalMap.reset(PreparedStatement.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package datadog.trace.instrumentation.jdbc;
|
package datadog.trace.instrumentation.jdbc;
|
||||||
|
|
||||||
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
import static datadog.trace.agent.tooling.ByteBuddyElementMatchers.safeHasSuperType;
|
||||||
import static io.opentracing.log.Fields.ERROR_OBJECT;
|
import static datadog.trace.instrumentation.jdbc.JDBCDecorator.DECORATE;
|
||||||
|
import static datadog.trace.instrumentation.jdbc.JDBCUtils.connectionFromStatement;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
|
@ -12,21 +13,13 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
import datadog.trace.agent.tooling.Instrumenter;
|
import datadog.trace.agent.tooling.Instrumenter;
|
||||||
import datadog.trace.api.DDSpanTypes;
|
|
||||||
import datadog.trace.api.DDTags;
|
|
||||||
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
|
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
|
||||||
import datadog.trace.bootstrap.ExceptionLogger;
|
|
||||||
import datadog.trace.bootstrap.JDBCMaps;
|
|
||||||
import io.opentracing.Scope;
|
import io.opentracing.Scope;
|
||||||
import io.opentracing.Span;
|
import io.opentracing.Span;
|
||||||
import io.opentracing.noop.NoopScopeManager.NoopScope;
|
import io.opentracing.noop.NoopScopeManager;
|
||||||
import io.opentracing.tag.Tags;
|
|
||||||
import io.opentracing.util.GlobalTracer;
|
import io.opentracing.util.GlobalTracer;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DatabaseMetaData;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
import net.bytebuddy.description.method.MethodDescription;
|
import net.bytebuddy.description.method.MethodDescription;
|
||||||
|
@ -45,6 +38,19 @@ public final class StatementInstrumentation extends Instrumenter.Default {
|
||||||
return not(isInterface()).and(safeHasSuperType(named("java.sql.Statement")));
|
return not(isInterface()).and(safeHasSuperType(named("java.sql.Statement")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||||
return singletonMap(
|
return singletonMap(
|
||||||
|
@ -61,79 +67,18 @@ public final class StatementInstrumentation extends Instrumenter.Default {
|
||||||
if (callDepth > 0) {
|
if (callDepth > 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Connection connection;
|
|
||||||
try {
|
final Connection connection = connectionFromStatement(statement);
|
||||||
connection = statement.getConnection();
|
if (connection == null) {
|
||||||
try {
|
return NoopScopeManager.NoopScope.INSTANCE;
|
||||||
// unwrap the connection to cache the underlying actual connection and to not cache proxy
|
|
||||||
// objects
|
|
||||||
if (connection.isWrapperFor(Connection.class)) {
|
|
||||||
connection = connection.unwrap(Connection.class);
|
|
||||||
}
|
|
||||||
} catch (final Exception | AbstractMethodError e) {
|
|
||||||
// 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)
|
|
||||||
// Stick with original connection.
|
|
||||||
}
|
|
||||||
} catch (final Throwable e) {
|
|
||||||
// Had some problem getting the connection.
|
|
||||||
ExceptionLogger.LOGGER.debug("Could not get connection for StatementAdvice", e);
|
|
||||||
return NoopScope.INSTANCE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JDBCMaps.DBInfo dbInfo = JDBCMaps.connectionInfo.get(connection);
|
final Scope scope = GlobalTracer.get().buildSpan("database.query").startActive(true);
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* <p>This should be a util method to be shared between PreparedStatementInstrumentation and
|
|
||||||
* StatementInstrumentation class, but java.sql.* are on the platform classloaders in Java 9,
|
|
||||||
* which prevents us from referencing them in the bootstrap utils.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
if (dbInfo == null) {
|
|
||||||
try {
|
|
||||||
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);
|
|
||||||
} else {
|
|
||||||
dbInfo = JDBCMaps.DBInfo.DEFAULT;
|
|
||||||
}
|
|
||||||
} catch (final SQLException se) {
|
|
||||||
dbInfo = JDBCMaps.DBInfo.DEFAULT;
|
|
||||||
}
|
|
||||||
JDBCMaps.connectionInfo.put(connection, dbInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Scope scope =
|
|
||||||
GlobalTracer.get().buildSpan(dbInfo.getType() + ".query").startActive(true);
|
|
||||||
|
|
||||||
final Span span = scope.span();
|
final Span span = scope.span();
|
||||||
Tags.DB_TYPE.set(span, dbInfo.getType());
|
DECORATE.afterStart(span);
|
||||||
Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT);
|
DECORATE.onConnection(span, connection);
|
||||||
Tags.COMPONENT.set(span, "java-jdbc-statement");
|
DECORATE.onStatement(span, sql);
|
||||||
|
|
||||||
span.setTag(DDTags.SERVICE_NAME, dbInfo.getType());
|
|
||||||
span.setTag(DDTags.RESOURCE_NAME, sql);
|
|
||||||
span.setTag(DDTags.SPAN_TYPE, DDSpanTypes.SQL);
|
|
||||||
span.setTag("span.origin.type", statement.getClass().getName());
|
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 scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,11 +86,8 @@ public final class StatementInstrumentation extends Instrumenter.Default {
|
||||||
public static void stopSpan(
|
public static void stopSpan(
|
||||||
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
|
@Advice.Enter final Scope scope, @Advice.Thrown final Throwable throwable) {
|
||||||
if (scope != null) {
|
if (scope != null) {
|
||||||
if (throwable != null) {
|
DECORATE.onError(scope.span(), throwable);
|
||||||
final Span span = scope.span();
|
DECORATE.beforeFinish(scope.span());
|
||||||
Tags.ERROR.set(span, true);
|
|
||||||
span.log(Collections.singletonMap(ERROR_OBJECT, throwable));
|
|
||||||
}
|
|
||||||
scope.close();
|
scope.close();
|
||||||
CallDepthThreadLocalMap.reset(Statement.class);
|
CallDepthThreadLocalMap.reset(Statement.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,7 +171,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
|
||||||
"span.kind" Tags.SPAN_KIND_CLIENT
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
"span.type" DDSpanTypes.SQL
|
"span.type" DDSpanTypes.SQL
|
||||||
"component" "java-jdbc-statement"
|
"component" "java-jdbc-statement"
|
||||||
"db.jdbc.url" jdbcUrls.get(driver)
|
"db.instance" jdbcUrls.get(driver)
|
||||||
"span.origin.type" String
|
"span.origin.type" String
|
||||||
defaultTags()
|
defaultTags()
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
|
||||||
"span.kind" Tags.SPAN_KIND_CLIENT
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
"span.type" DDSpanTypes.SQL
|
"span.type" DDSpanTypes.SQL
|
||||||
"component" "java-jdbc-prepared_statement"
|
"component" "java-jdbc-prepared_statement"
|
||||||
"db.jdbc.url" jdbcUrls.get(driver)
|
"db.instance" jdbcUrls.get(driver)
|
||||||
"span.origin.type" String
|
"span.origin.type" String
|
||||||
defaultTags()
|
defaultTags()
|
||||||
}
|
}
|
||||||
|
@ -288,7 +288,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
|
||||||
"span.kind" Tags.SPAN_KIND_CLIENT
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
"span.type" DDSpanTypes.SQL
|
"span.type" DDSpanTypes.SQL
|
||||||
"component" "java-jdbc-prepared_statement"
|
"component" "java-jdbc-prepared_statement"
|
||||||
"db.jdbc.url" jdbcUrls.get(driver)
|
"db.instance" jdbcUrls.get(driver)
|
||||||
"span.origin.type" String
|
"span.origin.type" String
|
||||||
defaultTags()
|
defaultTags()
|
||||||
}
|
}
|
||||||
|
@ -344,7 +344,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
|
||||||
"span.kind" Tags.SPAN_KIND_CLIENT
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
"span.type" DDSpanTypes.SQL
|
"span.type" DDSpanTypes.SQL
|
||||||
"component" "java-jdbc-prepared_statement"
|
"component" "java-jdbc-prepared_statement"
|
||||||
"db.jdbc.url" jdbcUrls.get(driver)
|
"db.instance" jdbcUrls.get(driver)
|
||||||
"span.origin.type" String
|
"span.origin.type" String
|
||||||
defaultTags()
|
defaultTags()
|
||||||
}
|
}
|
||||||
|
@ -400,7 +400,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
|
||||||
"span.kind" Tags.SPAN_KIND_CLIENT
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
"span.type" DDSpanTypes.SQL
|
"span.type" DDSpanTypes.SQL
|
||||||
"component" "java-jdbc-statement"
|
"component" "java-jdbc-statement"
|
||||||
"db.jdbc.url" jdbcUrls.get(driver)
|
"db.instance" jdbcUrls.get(driver)
|
||||||
"span.origin.type" String
|
"span.origin.type" String
|
||||||
defaultTags()
|
defaultTags()
|
||||||
}
|
}
|
||||||
|
@ -459,7 +459,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
|
||||||
"span.kind" Tags.SPAN_KIND_CLIENT
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
"span.type" DDSpanTypes.SQL
|
"span.type" DDSpanTypes.SQL
|
||||||
"component" "java-jdbc-prepared_statement"
|
"component" "java-jdbc-prepared_statement"
|
||||||
"db.jdbc.url" jdbcUrls.get(driver)
|
"db.instance" jdbcUrls.get(driver)
|
||||||
"span.origin.type" String
|
"span.origin.type" String
|
||||||
defaultTags()
|
defaultTags()
|
||||||
}
|
}
|
||||||
|
@ -535,7 +535,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
|
||||||
}
|
}
|
||||||
"span.kind" Tags.SPAN_KIND_CLIENT
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
"span.type" DDSpanTypes.SQL
|
"span.type" DDSpanTypes.SQL
|
||||||
"db.jdbc.url" jdbcUrls.get(driver)
|
"db.instance" jdbcUrls.get(driver)
|
||||||
"span.origin.type" String
|
"span.origin.type" String
|
||||||
defaultTags()
|
defaultTags()
|
||||||
}
|
}
|
||||||
|
@ -591,27 +591,45 @@ class JDBCInstrumentationTest extends AgentTestRunner {
|
||||||
for (int i = 0; i < numQueries; ++i) {
|
for (int i = 0; i < numQueries; ++i) {
|
||||||
res[i] == 3
|
res[i] == 3
|
||||||
}
|
}
|
||||||
assertTraces(6) {
|
assertTraces(5) {
|
||||||
trace(0, 1) {
|
trace(0, 2) {
|
||||||
span(0) {
|
span(0) {
|
||||||
|
operationName "${dbType}.query"
|
||||||
|
serviceName dbType
|
||||||
|
resourceName query
|
||||||
|
spanType DDSpanTypes.SQL
|
||||||
|
errored false
|
||||||
|
tags {
|
||||||
|
"db.type" dbType
|
||||||
|
"db.user" "SA"
|
||||||
|
"component" "java-jdbc-prepared_statement"
|
||||||
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
|
"span.type" DDSpanTypes.SQL
|
||||||
|
"db.instance" jdbcUrls.get(dbType)
|
||||||
|
"span.origin.type" String
|
||||||
|
defaultTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span(1) {
|
||||||
operationName "${dbType}.query"
|
operationName "${dbType}.query"
|
||||||
serviceName dbType
|
serviceName dbType
|
||||||
resourceName "CALL USER()"
|
resourceName "CALL USER()"
|
||||||
spanType DDSpanTypes.SQL
|
spanType DDSpanTypes.SQL
|
||||||
errored false
|
errored false
|
||||||
|
childOf(span(0))
|
||||||
tags {
|
tags {
|
||||||
"db.type" "hsqldb"
|
"db.type" "hsqldb"
|
||||||
"db.user" "SA"
|
"db.user" "SA"
|
||||||
"component" "java-jdbc-statement"
|
"component" "java-jdbc-statement"
|
||||||
"span.kind" Tags.SPAN_KIND_CLIENT
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
"span.type" DDSpanTypes.SQL
|
"span.type" DDSpanTypes.SQL
|
||||||
"db.jdbc.url" jdbcUrls.get(dbType)
|
"db.instance" jdbcUrls.get(dbType)
|
||||||
"span.origin.type" String
|
"span.origin.type" String
|
||||||
defaultTags()
|
defaultTags()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 1; i < numQueries + 1; ++i) {
|
for (int i = 1; i < numQueries; ++i) {
|
||||||
trace(i, 1) {
|
trace(i, 1) {
|
||||||
span(0) {
|
span(0) {
|
||||||
operationName "${dbType}.query"
|
operationName "${dbType}.query"
|
||||||
|
@ -625,7 +643,7 @@ class JDBCInstrumentationTest extends AgentTestRunner {
|
||||||
"component" "java-jdbc-prepared_statement"
|
"component" "java-jdbc-prepared_statement"
|
||||||
"span.kind" Tags.SPAN_KIND_CLIENT
|
"span.kind" Tags.SPAN_KIND_CLIENT
|
||||||
"span.type" DDSpanTypes.SQL
|
"span.type" DDSpanTypes.SQL
|
||||||
"db.jdbc.url" jdbcUrls.get(dbType)
|
"db.instance" jdbcUrls.get(dbType)
|
||||||
"span.origin.type" String
|
"span.origin.type" String
|
||||||
defaultTags()
|
defaultTags()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue