Migrate JDBC instrumentation to Decorator

This commit is contained in:
Tyler Benson 2019-02-26 10:09:39 -08:00
parent 0cdc80aa14
commit c46909e59f
11 changed files with 245 additions and 195 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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