Refactor DatabaseClientTracer (in preparation for low-cardinality span names) (#2398)

* Refactor DatabaseClientTracer (in preparation for low-cardinality span names)

* spotless
This commit is contained in:
Mateusz Rzeszutek 2021-02-26 15:36:24 +01:00 committed by GitHub
parent 605485b998
commit fd55ce226a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 430 additions and 312 deletions

View File

@ -8,16 +8,22 @@ package io.opentelemetry.instrumentation.api.tracer;
import static io.opentelemetry.api.trace.SpanKind.CLIENT; import static io.opentelemetry.api.trace.SpanKind.CLIENT;
import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.utils.NetPeerUtils; import io.opentelemetry.instrumentation.api.tracer.utils.NetPeerUtils;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.concurrent.ExecutionException; import org.checkerframework.checker.nullness.qual.Nullable;
public abstract class DatabaseClientTracer<CONNECTION, QUERY> extends BaseTracer {
/**
* Base class for implementing Tracers for database clients.
*
* @param <CONNECTION> type of the database connection.
* @param <STATEMENT> type of the database statement being executed.
* @param <SANITIZEDSTATEMENT> type of the database statement after sanitization.
*/
public abstract class DatabaseClientTracer<CONNECTION, STATEMENT, SANITIZEDSTATEMENT>
extends BaseTracer {
protected static final String DB_QUERY = "DB Query"; protected static final String DB_QUERY = "DB Query";
public DatabaseClientTracer() {} public DatabaseClientTracer() {}
@ -30,61 +36,68 @@ public abstract class DatabaseClientTracer<CONNECTION, QUERY> extends BaseTracer
return shouldStartSpan(CLIENT, parentContext); return shouldStartSpan(CLIENT, parentContext);
} }
public Context startSpan(Context parentContext, CONNECTION connection, QUERY query) { public Context startSpan(Context parentContext, CONNECTION connection, STATEMENT statement) {
String normalizedQuery = normalizeQuery(query); SANITIZEDSTATEMENT sanitizedStatement = sanitizeStatement(statement);
Span span = SpanBuilder span =
tracer tracer
.spanBuilder(spanName(connection, query, normalizedQuery)) .spanBuilder(spanName(connection, statement, sanitizedStatement))
.setParent(parentContext) .setParent(parentContext)
.setSpanKind(CLIENT) .setSpanKind(CLIENT)
.setAttribute(SemanticAttributes.DB_SYSTEM, dbSystem(connection)) .setAttribute(SemanticAttributes.DB_SYSTEM, dbSystem(connection));
.startSpan();
if (connection != null) { if (connection != null) {
onConnection(span, connection); onConnection(span, connection);
setNetSemanticConvention(span, connection); setNetSemanticConvention(span, connection);
} }
onStatement(span, normalizedQuery); onStatement(span, connection, statement, sanitizedStatement);
return withClientSpan(parentContext, span); return withClientSpan(parentContext, span.startSpan());
} }
public void endExceptionally(Context context, Throwable throwable) { protected abstract SANITIZEDSTATEMENT sanitizeStatement(STATEMENT statement);
Span span = Span.fromContext(context);
onError(span, throwable); protected String spanName(
end(span); CONNECTION connection, STATEMENT statement, SANITIZEDSTATEMENT sanitizedStatement) {
return conventionSpanName(
dbName(connection), dbOperation(connection, statement, sanitizedStatement), null);
} }
/**
* A helper method for constructing the span name formatting according to DB semantic conventions:
* {@code <db.operation> <db.name><table>}.
*/
public static String conventionSpanName(
@Nullable String dbName, @Nullable String operation, @Nullable String table) {
if (operation == null) {
return dbName == null ? DB_QUERY : dbName;
}
StringBuilder name = new StringBuilder(operation);
if (dbName != null || table != null) {
name.append(' ');
}
if (dbName != null) {
name.append(dbName);
if (table != null) {
name.append('.');
}
}
if (table != null) {
name.append(table);
}
return name.toString();
}
protected abstract String dbSystem(CONNECTION connection);
/** This should be called when the connection is being used, not when it's created. */ /** This should be called when the connection is being used, not when it's created. */
protected Span onConnection(Span span, CONNECTION connection) { protected void onConnection(SpanBuilder span, CONNECTION connection) {
span.setAttribute(SemanticAttributes.DB_USER, dbUser(connection)); span.setAttribute(SemanticAttributes.DB_USER, dbUser(connection));
span.setAttribute(SemanticAttributes.DB_NAME, dbName(connection)); span.setAttribute(SemanticAttributes.DB_NAME, dbName(connection));
span.setAttribute(SemanticAttributes.DB_CONNECTION_STRING, dbConnectionString(connection)); span.setAttribute(SemanticAttributes.DB_CONNECTION_STRING, dbConnectionString(connection));
return span;
} }
@Override
protected void onError(Span span, Throwable throwable) {
if (throwable != null) {
span.setStatus(StatusCode.ERROR);
addThrowable(
span, throwable instanceof ExecutionException ? throwable.getCause() : throwable);
}
}
protected void setNetSemanticConvention(Span span, CONNECTION connection) {
NetPeerUtils.INSTANCE.setNetPeer(span, peerAddress(connection));
}
protected void onStatement(Span span, String statement) {
span.setAttribute(SemanticAttributes.DB_STATEMENT, statement);
}
protected abstract String normalizeQuery(QUERY query);
protected abstract String dbSystem(CONNECTION connection);
protected String dbUser(CONNECTION connection) { protected String dbUser(CONNECTION connection) {
return null; return null;
} }
@ -97,17 +110,30 @@ public abstract class DatabaseClientTracer<CONNECTION, QUERY> extends BaseTracer
return null; return null;
} }
protected void setNetSemanticConvention(SpanBuilder span, CONNECTION connection) {
NetPeerUtils.INSTANCE.setNetPeer(span, peerAddress(connection));
}
protected abstract InetSocketAddress peerAddress(CONNECTION connection); protected abstract InetSocketAddress peerAddress(CONNECTION connection);
protected String spanName(CONNECTION connection, QUERY query, String normalizedQuery) { protected void onStatement(
if (normalizedQuery != null) { SpanBuilder span,
return normalizedQuery; CONNECTION connection,
STATEMENT statement,
SANITIZEDSTATEMENT sanitizedStatement) {
span.setAttribute(
SemanticAttributes.DB_STATEMENT, dbStatement(connection, statement, sanitizedStatement));
span.setAttribute(
SemanticAttributes.DB_OPERATION, dbOperation(connection, statement, sanitizedStatement));
} }
String result = null; protected String dbStatement(
if (connection != null) { CONNECTION connection, STATEMENT statement, SANITIZEDSTATEMENT sanitizedStatement) {
result = dbName(connection); return null;
} }
return result == null ? DB_QUERY : result;
protected String dbOperation(
CONNECTION connection, STATEMENT statement, SANITIZEDSTATEMENT sanitizedStatement) {
return null;
} }
} }

View File

@ -9,15 +9,18 @@ import com.datastax.driver.core.ExecutionInfo;
import com.datastax.driver.core.Host; import com.datastax.driver.core.Host;
import com.datastax.driver.core.Session; import com.datastax.driver.core.Session;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.instrumentation.api.tracer.utils.NetPeerUtils; import io.opentelemetry.instrumentation.api.tracer.utils.NetPeerUtils;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementInfo;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer; import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
public class CassandraDatabaseClientTracer extends DatabaseClientTracer<Session, String> { public class CassandraDatabaseClientTracer
extends DatabaseClientTracer<Session, String, SqlStatementInfo> {
private static final CassandraDatabaseClientTracer TRACER = new CassandraDatabaseClientTracer(); private static final CassandraDatabaseClientTracer TRACER = new CassandraDatabaseClientTracer();
public static CassandraDatabaseClientTracer tracer() { public static CassandraDatabaseClientTracer tracer() {
@ -30,8 +33,23 @@ public class CassandraDatabaseClientTracer extends DatabaseClientTracer<Session,
} }
@Override @Override
protected String normalizeQuery(String query) { protected SqlStatementInfo sanitizeStatement(String statement) {
return SqlStatementSanitizer.sanitize(query).getFullStatement(); return SqlStatementSanitizer.sanitize(statement);
}
// TODO: use the <operation> <db.name>.<table> naming scheme
protected String spanName(
Session connection, String statement, SqlStatementInfo sanitizedStatement) {
String fullStatement = sanitizedStatement.getFullStatement();
if (fullStatement != null) {
return fullStatement;
}
String result = null;
if (connection != null) {
result = dbName(connection);
}
return result == null ? DB_QUERY : result;
} }
@Override @Override
@ -39,20 +57,26 @@ public class CassandraDatabaseClientTracer extends DatabaseClientTracer<Session,
return DbSystemValues.CASSANDRA; return DbSystemValues.CASSANDRA;
} }
@Override
protected void onConnection(SpanBuilder span, Session session) {
span.setAttribute(SemanticAttributes.DB_CASSANDRA_KEYSPACE, session.getLoggedKeyspace());
super.onConnection(span, session);
}
@Override @Override
protected String dbName(Session session) { protected String dbName(Session session) {
return session.getLoggedKeyspace(); return session.getLoggedKeyspace();
} }
@Override @Override
protected Span onConnection(Span span, Session session) { protected InetSocketAddress peerAddress(Session session) {
span.setAttribute(SemanticAttributes.DB_CASSANDRA_KEYSPACE, session.getLoggedKeyspace()); return null;
return super.onConnection(span, session);
} }
@Override @Override
protected InetSocketAddress peerAddress(Session session) { protected String dbStatement(
return null; Session connection, String statement, SqlStatementInfo sanitizedStatement) {
return sanitizedStatement.getFullStatement();
} }
public void end(Context context, ExecutionInfo executionInfo) { public void end(Context context, ExecutionInfo executionInfo) {

View File

@ -14,16 +14,19 @@ import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
import com.datastax.oss.driver.api.core.cql.Statement; import com.datastax.oss.driver.api.core.cql.Statement;
import com.datastax.oss.driver.api.core.metadata.Node; import com.datastax.oss.driver.api.core.metadata.Node;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.instrumentation.api.tracer.utils.NetPeerUtils; import io.opentelemetry.instrumentation.api.tracer.utils.NetPeerUtils;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementInfo;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer; import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
public class CassandraDatabaseClientTracer extends DatabaseClientTracer<CqlSession, String> { public class CassandraDatabaseClientTracer
extends DatabaseClientTracer<CqlSession, String, SqlStatementInfo> {
private static final CassandraDatabaseClientTracer TRACER = new CassandraDatabaseClientTracer(); private static final CassandraDatabaseClientTracer TRACER = new CassandraDatabaseClientTracer();
@ -37,8 +40,23 @@ public class CassandraDatabaseClientTracer extends DatabaseClientTracer<CqlSessi
} }
@Override @Override
protected String normalizeQuery(String query) { protected SqlStatementInfo sanitizeStatement(String statement) {
return SqlStatementSanitizer.sanitize(query).getFullStatement(); return SqlStatementSanitizer.sanitize(statement);
}
// TODO: use the <operation> <db.name>.<table> naming scheme
protected String spanName(
CqlSession connection, String statement, SqlStatementInfo sanitizedStatement) {
String fullStatement = sanitizedStatement.getFullStatement();
if (fullStatement != null) {
return fullStatement;
}
String result = null;
if (connection != null) {
result = dbName(connection);
}
return result == null ? DB_QUERY : result;
} }
@Override @Override
@ -127,9 +145,13 @@ public class CassandraDatabaseClientTracer extends DatabaseClientTracer<CqlSessi
} }
@Override @Override
protected void onStatement(Span span, String statement) { protected void onStatement(
super.onStatement(span, statement); SpanBuilder span,
String table = SqlStatementSanitizer.sanitize(statement).getTable(); CqlSession connection,
String statement,
SqlStatementInfo sanitizedStatement) {
super.onStatement(span, connection, statement, sanitizedStatement);
String table = sanitizedStatement.getTable();
if (table != null) { if (table != null) {
// account for splitting out the keyspace, <keyspace>.<table> // account for splitting out the keyspace, <keyspace>.<table>
int i = table.indexOf('.'); int i = table.indexOf('.');
@ -139,4 +161,10 @@ public class CassandraDatabaseClientTracer extends DatabaseClientTracer<CqlSessi
span.setAttribute(SemanticAttributes.DB_CASSANDRA_TABLE, table); span.setAttribute(SemanticAttributes.DB_CASSANDRA_TABLE, table);
} }
} }
@Override
protected String dbStatement(
CqlSession connection, String statement, SqlStatementInfo sanitizedStatement) {
return sanitizedStatement.getFullStatement();
}
} }

View File

@ -10,7 +10,7 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValu
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
public class CouchbaseClientTracer extends DatabaseClientTracer<Void, Method> { public class CouchbaseClientTracer extends DatabaseClientTracer<Void, Method, Void> {
private static final CouchbaseClientTracer TRACER = new CouchbaseClientTracer(); private static final CouchbaseClientTracer TRACER = new CouchbaseClientTracer();
public static CouchbaseClientTracer tracer() { public static CouchbaseClientTracer tracer() {
@ -18,13 +18,18 @@ public class CouchbaseClientTracer extends DatabaseClientTracer<Void, Method> {
} }
@Override @Override
protected String normalizeQuery(Method method) { protected String spanName(Void connection, Method method, Void sanitizedStatement) {
Class<?> declaringClass = method.getDeclaringClass(); Class<?> declaringClass = method.getDeclaringClass();
String className = String className =
declaringClass.getSimpleName().replace("CouchbaseAsync", "").replace("DefaultAsync", ""); declaringClass.getSimpleName().replace("CouchbaseAsync", "").replace("DefaultAsync", "");
return className + "." + method.getName(); return className + "." + method.getName();
} }
@Override
protected Void sanitizeStatement(Method method) {
return null;
}
@Override @Override
protected String dbSystem(Void connection) { protected String dbSystem(Void connection) {
return DbSystemValues.COUCHBASE; return DbSystemValues.COUCHBASE;

View File

@ -13,7 +13,7 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import org.elasticsearch.client.Response; import org.elasticsearch.client.Response;
public class ElasticsearchRestClientTracer extends DatabaseClientTracer<Void, String> { public class ElasticsearchRestClientTracer extends DatabaseClientTracer<Void, String, String> {
private static final ElasticsearchRestClientTracer TRACER = new ElasticsearchRestClientTracer(); private static final ElasticsearchRestClientTracer TRACER = new ElasticsearchRestClientTracer();
public static ElasticsearchRestClientTracer tracer() { public static ElasticsearchRestClientTracer tracer() {
@ -29,13 +29,8 @@ public class ElasticsearchRestClientTracer extends DatabaseClientTracer<Void, St
} }
@Override @Override
protected void onStatement(Span span, String statement) { protected String sanitizeStatement(String operation) {
span.setAttribute(SemanticAttributes.DB_OPERATION, statement); return operation;
}
@Override
protected String normalizeQuery(String query) {
return query;
} }
@Override @Override
@ -48,6 +43,11 @@ public class ElasticsearchRestClientTracer extends DatabaseClientTracer<Void, St
return null; return null;
} }
@Override
protected String dbOperation(Void connection, String operation, String ignored) {
return operation;
}
@Override @Override
protected String getInstrumentationName() { protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.elasticsearch"; return "io.opentelemetry.javaagent.elasticsearch";

View File

@ -8,12 +8,11 @@ package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import org.elasticsearch.action.Action; import org.elasticsearch.action.Action;
public class ElasticsearchTransportClientTracer public class ElasticsearchTransportClientTracer
extends DatabaseClientTracer<Void, Action<?, ?, ?>> { extends DatabaseClientTracer<Void, Action<?, ?, ?>, String> {
private static final ElasticsearchTransportClientTracer TRACER = private static final ElasticsearchTransportClientTracer TRACER =
new ElasticsearchTransportClientTracer(); new ElasticsearchTransportClientTracer();
@ -21,15 +20,15 @@ public class ElasticsearchTransportClientTracer
return TRACER; return TRACER;
} }
public void onRequest(Context context, Class action, Class request) { public void onRequest(Context context, Class<?> action, Class<?> request) {
Span span = Span.fromContext(context); Span span = Span.fromContext(context);
span.setAttribute("elasticsearch.action", action.getSimpleName()); span.setAttribute("elasticsearch.action", action.getSimpleName());
span.setAttribute("elasticsearch.request", request.getSimpleName()); span.setAttribute("elasticsearch.request", request.getSimpleName());
} }
@Override @Override
protected String normalizeQuery(Action<?, ?, ?> query) { protected String sanitizeStatement(Action<?, ?, ?> action) {
return query.getClass().getSimpleName(); return action.getClass().getSimpleName();
} }
@Override @Override
@ -43,8 +42,8 @@ public class ElasticsearchTransportClientTracer
} }
@Override @Override
protected void onStatement(Span span, String statement) { protected String dbOperation(Void connection, Action<?, ?, ?> action, String operation) {
span.setAttribute(SemanticAttributes.DB_OPERATION, statement); return operation;
} }
@Override @Override

View File

@ -8,13 +8,15 @@ package io.opentelemetry.javaagent.instrumentation.geode;
import static io.opentelemetry.api.trace.SpanKind.CLIENT; import static io.opentelemetry.api.trace.SpanKind.CLIENT;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementInfo;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer; import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import org.apache.geode.cache.Region; import org.apache.geode.cache.Region;
public class GeodeTracer extends DatabaseClientTracer<Region<?, ?>, String> { public class GeodeTracer extends DatabaseClientTracer<Region<?, ?>, String, SqlStatementInfo> {
private static final GeodeTracer TRACER = new GeodeTracer(); private static final GeodeTracer TRACER = new GeodeTracer();
public static GeodeTracer tracer() { public static GeodeTracer tracer() {
@ -22,33 +24,30 @@ public class GeodeTracer extends DatabaseClientTracer<Region<?, ?>, String> {
} }
public Span startSpan(String operation, Region<?, ?> connection, String query) { public Span startSpan(String operation, Region<?, ?> connection, String query) {
String normalizedQuery = normalizeQuery(query); SqlStatementInfo sanitizedStatement = sanitizeStatement(query);
Span span = SpanBuilder span =
tracer tracer
.spanBuilder(operation) .spanBuilder(operation)
.setSpanKind(CLIENT) .setSpanKind(CLIENT)
.setAttribute(SemanticAttributes.DB_SYSTEM, dbSystem(connection)) .setAttribute(SemanticAttributes.DB_SYSTEM, dbSystem(connection))
.setAttribute(SemanticAttributes.DB_OPERATION, operation) .setAttribute(SemanticAttributes.DB_OPERATION, operation);
.startSpan();
onConnection(span, connection); onConnection(span, connection);
setNetSemanticConvention(span, connection); setNetSemanticConvention(span, connection);
onStatement(span, normalizedQuery); onStatement(span, connection, query, sanitizedStatement);
return span; return span.startSpan();
} }
@Override @Override
protected String normalizeQuery(String query) { protected SqlStatementInfo sanitizeStatement(String statement) {
return SqlStatementSanitizer.sanitize(query).getFullStatement(); return SqlStatementSanitizer.sanitize(statement);
} }
@Override @Override
protected String dbSystem(Region<?, ?> region) { protected String dbSystem(Region<?, ?> region) {
// TODO(anuraaga): Replace with semantic attribute return SemanticAttributes.DbSystemValues.GEODE;
// https://github.com/open-telemetry/opentelemetry-specification/pull/1321
return "geode";
} }
@Override @Override
@ -61,6 +60,12 @@ public class GeodeTracer extends DatabaseClientTracer<Region<?, ?>, String> {
return null; return null;
} }
@Override
protected String dbStatement(
Region<?, ?> connection, String statement, SqlStatementInfo sanitizedStatement) {
return sanitizedStatement.getFullStatement();
}
@Override @Override
protected String getInstrumentationName() { protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.geode"; return "io.opentelemetry.javaagent.geode";

View File

@ -14,7 +14,6 @@ import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation; import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.util.Map; import java.util.Map;
@ -49,7 +48,7 @@ public class ConnectionInstrumentation implements TypeInstrumentation {
@Advice.OnMethodExit(suppress = Throwable.class) @Advice.OnMethodExit(suppress = Throwable.class)
public static void addDbInfo( public static void addDbInfo(
@Advice.Argument(0) String sql, @Advice.Return PreparedStatement statement) { @Advice.Argument(0) String sql, @Advice.Return PreparedStatement statement) {
JdbcMaps.preparedStatements.put(statement, SqlStatementSanitizer.sanitize(sql)); JdbcMaps.preparedStatements.put(statement, sql);
} }
} }
} }

View File

@ -8,7 +8,6 @@ package io.opentelemetry.javaagent.instrumentation.jdbc;
import static io.opentelemetry.javaagent.instrumentation.api.WeakMap.Provider.newWeakMap; import static io.opentelemetry.javaagent.instrumentation.api.WeakMap.Provider.newWeakMap;
import io.opentelemetry.javaagent.instrumentation.api.WeakMap; import io.opentelemetry.javaagent.instrumentation.api.WeakMap;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementInfo;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@ -19,6 +18,5 @@ import java.sql.PreparedStatement;
*/ */
public class JdbcMaps { public class JdbcMaps {
public static final WeakMap<Connection, DbInfo> connectionInfo = newWeakMap(); public static final WeakMap<Connection, DbInfo> connectionInfo = newWeakMap();
public static final WeakMap<PreparedStatement, SqlStatementInfo> preparedStatements = public static final WeakMap<PreparedStatement, String> preparedStatements = newWeakMap();
newWeakMap();
} }

View File

@ -9,8 +9,6 @@ import static io.opentelemetry.javaagent.instrumentation.jdbc.JdbcUtils.connecti
import io.opentelemetry.context.Context; import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.api.CallDepthThreadLocalMap;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementInfo; import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementInfo;
import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer; import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -20,7 +18,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
public class JdbcTracer extends DatabaseClientTracer<DbInfo, SqlStatementInfo> { public class JdbcTracer extends DatabaseClientTracer<DbInfo, String, SqlStatementInfo> {
private static final JdbcTracer TRACER = new JdbcTracer(); private static final JdbcTracer TRACER = new JdbcTracer();
public static JdbcTracer tracer() { public static JdbcTracer tracer() {
@ -32,6 +30,33 @@ public class JdbcTracer extends DatabaseClientTracer<DbInfo, SqlStatementInfo> {
return "io.opentelemetry.javaagent.jdbc"; return "io.opentelemetry.javaagent.jdbc";
} }
public Context startSpan(Context parentContext, PreparedStatement statement) {
return startSpan(parentContext, statement, JdbcMaps.preparedStatements.get(statement));
}
public Context startSpan(Context parentContext, Statement statement, String query) {
Connection connection = connectionFromStatement(statement);
if (connection == null) {
return null;
}
DbInfo dbInfo = extractDbInfo(connection);
return startSpan(parentContext, dbInfo, query);
}
@Override
protected SqlStatementInfo sanitizeStatement(String statement) {
return SqlStatementSanitizer.sanitize(statement);
}
@Override
protected String spanName(
DbInfo connection, String statement, SqlStatementInfo sanitizedStatement) {
return conventionSpanName(
dbName(connection), sanitizedStatement.getOperation(), sanitizedStatement.getTable());
}
@Override @Override
protected String dbSystem(DbInfo info) { protected String dbSystem(DbInfo info) {
return info.getSystem(); return info.getSystem();
@ -51,6 +76,11 @@ public class JdbcTracer extends DatabaseClientTracer<DbInfo, SqlStatementInfo> {
} }
} }
@Override
protected String dbConnectionString(DbInfo info) {
return info.getShortUrl();
}
// TODO find a way to implement // TODO find a way to implement
@Override @Override
protected InetSocketAddress peerAddress(DbInfo dbInfo) { protected InetSocketAddress peerAddress(DbInfo dbInfo) {
@ -58,58 +88,9 @@ public class JdbcTracer extends DatabaseClientTracer<DbInfo, SqlStatementInfo> {
} }
@Override @Override
protected String dbConnectionString(DbInfo info) { protected String dbStatement(
return info.getShortUrl(); DbInfo connection, String statement, SqlStatementInfo sanitizedStatement) {
} return sanitizedStatement.getFullStatement();
public CallDepth getCallDepth() {
return CallDepthThreadLocalMap.getCallDepth(Statement.class);
}
public Context startSpan(Context parentContext, PreparedStatement statement) {
return startSpan(parentContext, statement, JdbcMaps.preparedStatements.get(statement));
}
public Context startSpan(Context parentContext, Statement statement, String query) {
return startSpan(parentContext, statement, SqlStatementSanitizer.sanitize(query));
}
private Context startSpan(
Context parentContext, Statement statement, SqlStatementInfo queryInfo) {
Connection connection = connectionFromStatement(statement);
if (connection == null) {
return null;
}
DbInfo dbInfo = extractDbInfo(connection);
return startSpan(parentContext, dbInfo, queryInfo);
}
@Override
protected String normalizeQuery(SqlStatementInfo query) {
return query.getFullStatement();
}
@Override
protected String spanName(DbInfo connection, SqlStatementInfo query, String normalizedQuery) {
String dbName = dbName(connection);
if (query.getOperation() == null) {
return dbName == null ? DB_QUERY : dbName;
}
StringBuilder name = new StringBuilder();
name.append(query.getOperation()).append(' ');
if (dbName != null) {
name.append(dbName);
if (query.getTable() != null) {
name.append('.');
}
}
if (query.getTable() != null) {
name.append(query.getTable());
}
return name.toString();
} }
private DbInfo extractDbInfo(Connection connection) { private DbInfo extractDbInfo(Connection connection) {

View File

@ -15,7 +15,7 @@ import java.util.List;
import redis.clients.jedis.Connection; import redis.clients.jedis.Connection;
import redis.clients.jedis.Protocol.Command; import redis.clients.jedis.Protocol.Command;
public class JedisClientTracer extends DatabaseClientTracer<Connection, CommandWithArgs> { public class JedisClientTracer extends DatabaseClientTracer<Connection, CommandWithArgs, String> {
private static final JedisClientTracer TRACER = new JedisClientTracer(); private static final JedisClientTracer TRACER = new JedisClientTracer();
public static JedisClientTracer tracer() { public static JedisClientTracer tracer() {
@ -23,13 +23,14 @@ public class JedisClientTracer extends DatabaseClientTracer<Connection, CommandW
} }
@Override @Override
protected String spanName(Connection connection, CommandWithArgs query, String normalizedQuery) { protected String sanitizeStatement(CommandWithArgs command) {
return query.getStringCommand(); return RedisCommandSanitizer.sanitize(command.getStringCommand(), command.getArgs());
} }
@Override @Override
protected String normalizeQuery(CommandWithArgs command) { protected String spanName(
return RedisCommandSanitizer.sanitize(command.getStringCommand(), command.getArgs()); Connection connection, CommandWithArgs command, String sanitizedStatement) {
return command.getStringCommand();
} }
@Override @Override
@ -47,6 +48,12 @@ public class JedisClientTracer extends DatabaseClientTracer<Connection, CommandW
return new InetSocketAddress(connection.getHost(), connection.getPort()); return new InetSocketAddress(connection.getHost(), connection.getPort());
} }
@Override
protected String dbStatement(
Connection connection, CommandWithArgs command, String sanitizedStatement) {
return sanitizedStatement;
}
@Override @Override
protected String getInstrumentationName() { protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.jedis"; return "io.opentelemetry.javaagent.jedis";

View File

@ -17,7 +17,7 @@ import redis.clients.jedis.Connection;
import redis.clients.jedis.Protocol; import redis.clients.jedis.Protocol;
import redis.clients.jedis.commands.ProtocolCommand; import redis.clients.jedis.commands.ProtocolCommand;
public class JedisClientTracer extends DatabaseClientTracer<Connection, CommandWithArgs> { public class JedisClientTracer extends DatabaseClientTracer<Connection, CommandWithArgs, String> {
private static final JedisClientTracer TRACER = new JedisClientTracer(); private static final JedisClientTracer TRACER = new JedisClientTracer();
public static JedisClientTracer tracer() { public static JedisClientTracer tracer() {
@ -25,13 +25,14 @@ public class JedisClientTracer extends DatabaseClientTracer<Connection, CommandW
} }
@Override @Override
protected String spanName(Connection connection, CommandWithArgs query, String normalizedQuery) { protected String sanitizeStatement(CommandWithArgs command) {
return query.getStringCommand(); return RedisCommandSanitizer.sanitize(command.getStringCommand(), command.getArgs());
} }
@Override @Override
protected String normalizeQuery(CommandWithArgs command) { protected String spanName(
return RedisCommandSanitizer.sanitize(command.getStringCommand(), command.getArgs()); Connection connection, CommandWithArgs command, String sanitizedStatement) {
return command.getStringCommand();
} }
@Override @Override
@ -49,6 +50,12 @@ public class JedisClientTracer extends DatabaseClientTracer<Connection, CommandW
return new InetSocketAddress(connection.getHost(), connection.getPort()); return new InetSocketAddress(connection.getHost(), connection.getPort());
} }
@Override
protected String dbStatement(
Connection connection, CommandWithArgs command, String sanitizedStatement) {
return sanitizedStatement;
}
@Override @Override
protected String getInstrumentationName() { protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.jedis"; return "io.opentelemetry.javaagent.jedis";

View File

@ -6,14 +6,28 @@
package io.opentelemetry.javaagent.instrumentation.lettuce.v4_0; package io.opentelemetry.javaagent.instrumentation.lettuce.v4_0;
import com.lambdaworks.redis.RedisURI; import com.lambdaworks.redis.RedisURI;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
public abstract class LettuceAbstractDatabaseClientTracer<QUERY> public abstract class LettuceAbstractDatabaseClientTracer<STATEMENT>
extends DatabaseClientTracer<RedisURI, QUERY> { extends DatabaseClientTracer<RedisURI, STATEMENT, String> {
@Override
protected String spanName(RedisURI connection, STATEMENT statement, String operation) {
return operation;
}
@Override
public void onConnection(SpanBuilder span, RedisURI connection) {
if (connection != null && connection.getDatabase() != 0) {
span.setAttribute(
SemanticAttributes.DB_REDIS_DATABASE_INDEX, (long) connection.getDatabase());
}
super.onConnection(span, connection);
}
@Override @Override
protected String dbSystem(RedisURI connection) { protected String dbSystem(RedisURI connection) {
@ -26,11 +40,8 @@ public abstract class LettuceAbstractDatabaseClientTracer<QUERY>
} }
@Override @Override
public Span onConnection(Span span, RedisURI connection) { protected String dbStatement(RedisURI connection, STATEMENT statement, String operation) {
if (connection != null && connection.getDatabase() != 0) { return operation;
span.setAttribute(SemanticAttributes.DB_REDIS_DATABASE_INDEX, connection.getDatabase());
}
return super.onConnection(span, connection);
} }
@Override @Override

View File

@ -16,7 +16,7 @@ public class LettuceConnectionDatabaseClientTracer
} }
@Override @Override
protected String normalizeQuery(String command) { protected String sanitizeStatement(String command) {
return command; return command;
} }
} }

View File

@ -17,7 +17,7 @@ public class LettuceDatabaseClientTracer
} }
@Override @Override
protected String normalizeQuery(RedisCommand<?, ?, ?> command) { protected String sanitizeStatement(RedisCommand<?, ?, ?> command) {
return command.getType().name(); return command.getType().name();
} }
} }

View File

@ -6,14 +6,14 @@
package io.opentelemetry.javaagent.instrumentation.lettuce.v5_0; package io.opentelemetry.javaagent.instrumentation.lettuce.v5_0;
import io.lettuce.core.RedisURI; import io.lettuce.core.RedisURI;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
public abstract class LettuceAbstractDatabaseClientTracer<QUERY> public abstract class LettuceAbstractDatabaseClientTracer<STATEMENT>
extends DatabaseClientTracer<RedisURI, QUERY> { extends DatabaseClientTracer<RedisURI, STATEMENT, String> {
@Override @Override
protected String getInstrumentationName() { protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.lettuce"; return "io.opentelemetry.javaagent.lettuce";
@ -30,10 +30,17 @@ public abstract class LettuceAbstractDatabaseClientTracer<QUERY>
} }
@Override @Override
public Span onConnection(Span span, RedisURI connection) { public void onConnection(SpanBuilder span, RedisURI connection) {
if (connection != null && connection.getDatabase() != 0) { if (connection != null && connection.getDatabase() != 0) {
span.setAttribute(SemanticAttributes.DB_REDIS_DATABASE_INDEX, connection.getDatabase()); span.setAttribute(
SemanticAttributes.DB_REDIS_DATABASE_INDEX, (long) connection.getDatabase());
} }
return super.onConnection(span, connection); super.onConnection(span, connection);
}
@Override
protected String dbStatement(
RedisURI connection, STATEMENT statement, String sanitizedStatement) {
return sanitizedStatement;
} }
} }

View File

@ -37,10 +37,11 @@ public class LettuceAsyncBiFunction<T, U extends Throwable, R>
@Override @Override
public R apply(T t, Throwable throwable) { public R apply(T t, Throwable throwable) {
if (throwable instanceof CancellationException) { if (throwable == null) {
tracer().end(context);
} else if (throwable instanceof CancellationException) {
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) { if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
Span span = Span.fromContext(context); Span.fromContext(context).setAttribute("lettuce.command.cancelled", true);
span.setAttribute("lettuce.command.cancelled", true);
} }
tracer().end(context); tracer().end(context);
} else { } else {

View File

@ -5,6 +5,8 @@
package io.opentelemetry.javaagent.instrumentation.lettuce.v5_0; package io.opentelemetry.javaagent.instrumentation.lettuce.v5_0;
import io.lettuce.core.RedisURI;
public class LettuceConnectionDatabaseClientTracer public class LettuceConnectionDatabaseClientTracer
extends LettuceAbstractDatabaseClientTracer<String> { extends LettuceAbstractDatabaseClientTracer<String> {
private static final LettuceConnectionDatabaseClientTracer TRACER = private static final LettuceConnectionDatabaseClientTracer TRACER =
@ -15,7 +17,12 @@ public class LettuceConnectionDatabaseClientTracer
} }
@Override @Override
protected String normalizeQuery(String query) { protected String sanitizeStatement(String command) {
return query; return command;
}
@Override
protected String spanName(RedisURI connection, String command, String ignored) {
return command;
} }
} }

View File

@ -21,13 +21,7 @@ public class LettuceDatabaseClientTracer
} }
@Override @Override
protected String spanName( protected String sanitizeStatement(RedisCommand<?, ?, ?> redisCommand) {
RedisURI connection, RedisCommand<?, ?, ?> query, String normalizedQuery) {
return LettuceInstrumentationUtil.getCommandName(query);
}
@Override
protected String normalizeQuery(RedisCommand<?, ?, ?> redisCommand) {
String command = LettuceInstrumentationUtil.getCommandName(redisCommand); String command = LettuceInstrumentationUtil.getCommandName(redisCommand);
List<String> args = List<String> args =
redisCommand.getArgs() == null redisCommand.getArgs() == null
@ -35,4 +29,10 @@ public class LettuceDatabaseClientTracer
: LettuceArgSplitter.splitArgs(redisCommand.getArgs().toCommandString()); : LettuceArgSplitter.splitArgs(redisCommand.getArgs().toCommandString());
return RedisCommandSanitizer.sanitize(command, args); return RedisCommandSanitizer.sanitize(command, args);
} }
@Override
protected String spanName(
RedisURI connection, RedisCommand<?, ?, ?> command, String sanitizedStatement) {
return LettuceInstrumentationUtil.getCommandName(command);
}
} }

View File

@ -10,7 +10,7 @@ import static java.util.Arrays.asList;
import com.mongodb.ServerAddress; import com.mongodb.ServerAddress;
import com.mongodb.connection.ConnectionDescription; import com.mongodb.connection.ConnectionDescription;
import com.mongodb.event.CommandStartedEvent; import com.mongodb.event.CommandStartedEvent;
import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues;
@ -20,7 +20,6 @@ import java.lang.reflect.Method;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -30,7 +29,8 @@ import org.bson.BsonValue;
import org.bson.json.JsonWriter; import org.bson.json.JsonWriter;
import org.bson.json.JsonWriterSettings; import org.bson.json.JsonWriterSettings;
public class MongoClientTracer extends DatabaseClientTracer<CommandStartedEvent, BsonDocument> { public class MongoClientTracer
extends DatabaseClientTracer<CommandStartedEvent, BsonDocument, String> {
private static final MongoClientTracer TRACER = new MongoClientTracer(); private static final MongoClientTracer TRACER = new MongoClientTracer();
private final int maxNormalizedQueryLength; private final int maxNormalizedQueryLength;
@ -54,19 +54,34 @@ public class MongoClientTracer extends DatabaseClientTracer<CommandStartedEvent,
return "io.opentelemetry.javaagent.mongo"; return "io.opentelemetry.javaagent.mongo";
} }
@Override
protected String sanitizeStatement(BsonDocument command) {
StringWriter stringWriter = new StringWriter(128);
writeScrubbed(command, new JsonWriter(stringWriter, jsonWriterSettings), true);
// If using MongoDB driver >= 3.7, the substring invocation will be a no-op due to use of
// JsonWriterSettings.Builder.maxLength in the static initializer for JSON_WRITER_SETTINGS
return stringWriter
.getBuffer()
.substring(0, Math.min(maxNormalizedQueryLength, stringWriter.getBuffer().length()));
}
@Override
public String spanName(CommandStartedEvent event, BsonDocument document, String normalizedQuery) {
return conventionSpanName(dbName(event), event.getCommandName(), collectionName(event));
}
@Override @Override
protected String dbSystem(CommandStartedEvent event) { protected String dbSystem(CommandStartedEvent event) {
return DbSystemValues.MONGODB; return DbSystemValues.MONGODB;
} }
@Override @Override
protected Span onConnection(Span span, CommandStartedEvent event) { protected void onConnection(SpanBuilder span, CommandStartedEvent event) {
span.setAttribute(SemanticAttributes.DB_OPERATION, event.getCommandName());
String collection = collectionName(event); String collection = collectionName(event);
if (collection != null) { if (collection != null) {
span.setAttribute(SemanticAttributes.DB_MONGODB_COLLECTION, collection); span.setAttribute(SemanticAttributes.DB_MONGODB_COLLECTION, collection);
} }
return super.onConnection(span, event); super.onConnection(span, event);
} }
@Override @Override
@ -74,41 +89,6 @@ public class MongoClientTracer extends DatabaseClientTracer<CommandStartedEvent,
return event.getDatabaseName(); return event.getDatabaseName();
} }
@Override
protected InetSocketAddress peerAddress(CommandStartedEvent event) {
if (event.getConnectionDescription() != null
&& event.getConnectionDescription().getServerAddress() != null) {
return event.getConnectionDescription().getServerAddress().getSocketAddress();
} else {
return null;
}
}
@Override
public String spanName(CommandStartedEvent event, BsonDocument document, String normalizedQuery) {
String dbName = dbName(event);
if (event.getCommandName() == null) {
return dbName == null ? DB_QUERY : dbName;
}
String collectionName = collectionName(event);
StringBuilder name = new StringBuilder();
name.append(event.getCommandName());
if (dbName != null || collectionName != null) {
name.append(' ');
}
if (dbName != null) {
name.append(dbName);
if (collectionName != null) {
name.append('.');
}
}
if (collectionName != null) {
name.append(collectionName);
}
return name.toString();
}
@Override @Override
protected String dbConnectionString(CommandStartedEvent event) { protected String dbConnectionString(CommandStartedEvent event) {
ConnectionDescription connectionDescription = event.getConnectionDescription(); ConnectionDescription connectionDescription = event.getConnectionDescription();
@ -126,6 +106,28 @@ public class MongoClientTracer extends DatabaseClientTracer<CommandStartedEvent,
return null; return null;
} }
@Override
protected InetSocketAddress peerAddress(CommandStartedEvent event) {
if (event.getConnectionDescription() != null
&& event.getConnectionDescription().getServerAddress() != null) {
return event.getConnectionDescription().getServerAddress().getSocketAddress();
} else {
return null;
}
}
@Override
protected String dbStatement(
CommandStartedEvent event, BsonDocument command, String sanitizedStatement) {
return sanitizedStatement;
}
@Override
protected String dbOperation(
CommandStartedEvent event, BsonDocument command, String sanitizedStatement) {
return event.getCommandName();
}
private static final Method IS_TRUNCATED_METHOD; private static final Method IS_TRUNCATED_METHOD;
static { static {
@ -136,13 +138,6 @@ public class MongoClientTracer extends DatabaseClientTracer<CommandStartedEvent,
.orElse(null); .orElse(null);
} }
/**
* The values of these mongo fields will not be scrubbed out. This allows the non-sensitive
* collection names to be captured.
*/
private static final List<String> UNSCRUBBED_FIELDS =
asList("ordered", "insert", "count", "find", "create");
private JsonWriterSettings createJsonWriterSettings(int maxNormalizedQueryLength) { private JsonWriterSettings createJsonWriterSettings(int maxNormalizedQueryLength) {
JsonWriterSettings settings = null; JsonWriterSettings settings = null;
try { try {
@ -192,17 +187,6 @@ public class MongoClientTracer extends DatabaseClientTracer<CommandStartedEvent,
return settings; return settings;
} }
@Override
public String normalizeQuery(BsonDocument command) {
StringWriter stringWriter = new StringWriter(128);
writeScrubbed(command, new JsonWriter(stringWriter, jsonWriterSettings), true);
// If using MongoDB driver >= 3.7, the substring invocation will be a no-op due to use of
// JsonWriterSettings.Builder.maxLength in the static initializer for JSON_WRITER_SETTINGS
return stringWriter
.getBuffer()
.substring(0, Math.min(maxNormalizedQueryLength, stringWriter.getBuffer().length()));
}
private static final String HIDDEN_CHAR = "?"; private static final String HIDDEN_CHAR = "?";
private static boolean writeScrubbed(BsonDocument origin, JsonWriter writer, boolean isRoot) { private static boolean writeScrubbed(BsonDocument origin, JsonWriter writer, boolean isRoot) {

View File

@ -3,10 +3,9 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import com.mongodb.event.CommandStartedEvent
import static java.util.Arrays.asList import static java.util.Arrays.asList
import com.mongodb.event.CommandStartedEvent
import io.opentelemetry.javaagent.instrumentation.mongo.MongoClientTracer import io.opentelemetry.javaagent.instrumentation.mongo.MongoClientTracer
import org.bson.BsonArray import org.bson.BsonArray
import org.bson.BsonDocument import org.bson.BsonDocument
@ -15,21 +14,21 @@ import org.bson.BsonString
import spock.lang.Specification import spock.lang.Specification
class MongoClientTracerTest extends Specification { class MongoClientTracerTest extends Specification {
def 'should normalize queries to json'() { def 'should sanitize statements to json'() {
setup: setup:
def tracer = new MongoClientTracer() def tracer = new MongoClientTracer()
expect: expect:
normalizeQueryAcrossVersions(tracer, sanitizeStatementAcrossVersions(tracer,
new BsonDocument("cmd", new BsonInt32(1))) == new BsonDocument("cmd", new BsonInt32(1))) ==
'{"cmd": "?"}' '{"cmd": "?"}'
normalizeQueryAcrossVersions(tracer, sanitizeStatementAcrossVersions(tracer,
new BsonDocument("cmd", new BsonInt32(1)) new BsonDocument("cmd", new BsonInt32(1))
.append("sub", new BsonDocument("a", new BsonInt32(1)))) == .append("sub", new BsonDocument("a", new BsonInt32(1)))) ==
'{"cmd": "?", "sub": {"a": "?"}}' '{"cmd": "?", "sub": {"a": "?"}}'
normalizeQueryAcrossVersions(tracer, sanitizeStatementAcrossVersions(tracer,
new BsonDocument("cmd", new BsonInt32(1)) new BsonDocument("cmd", new BsonInt32(1))
.append("sub", new BsonArray(asList(new BsonInt32(1))))) == .append("sub", new BsonArray(asList(new BsonInt32(1))))) ==
'{"cmd": "?", "sub": ["?"]}' '{"cmd": "?", "sub": ["?"]}'
@ -40,7 +39,7 @@ class MongoClientTracerTest extends Specification {
def tracer = new MongoClientTracer() def tracer = new MongoClientTracer()
expect: expect:
normalizeQueryAcrossVersions(tracer, sanitizeStatementAcrossVersions(tracer,
new BsonDocument("cmd", new BsonString("c")) new BsonDocument("cmd", new BsonString("c"))
.append("f", new BsonString("c")) .append("f", new BsonString("c"))
.append("sub", new BsonString("c"))) == .append("sub", new BsonString("c"))) ==
@ -51,7 +50,7 @@ class MongoClientTracerTest extends Specification {
setup: setup:
def tracer = new MongoClientTracer(20) def tracer = new MongoClientTracer(20)
def normalized = normalizeQueryAcrossVersions(tracer, def normalized = sanitizeStatementAcrossVersions(tracer,
new BsonDocument("cmd", new BsonString("c")) new BsonDocument("cmd", new BsonString("c"))
.append("f1", new BsonString("c1")) .append("f1", new BsonString("c1"))
.append("f2", new BsonString("c2"))) .append("f2", new BsonString("c2")))
@ -64,7 +63,7 @@ class MongoClientTracerTest extends Specification {
setup: setup:
def tracer = new MongoClientTracer(27) def tracer = new MongoClientTracer(27)
def normalized = normalizeQueryAcrossVersions(tracer, def normalized = sanitizeStatementAcrossVersions(tracer,
new BsonDocument("cmd", new BsonString("c")) new BsonDocument("cmd", new BsonString("c"))
.append("f1", new BsonArray(Arrays.asList(new BsonString("c1"), new BsonString("c2")))) .append("f1", new BsonArray(Arrays.asList(new BsonString("c1"), new BsonString("c2"))))
.append("f2", new BsonString("c3"))) .append("f2", new BsonString("c3")))
@ -89,11 +88,11 @@ class MongoClientTracerTest extends Specification {
command = "listDatabases" command = "listDatabases"
} }
def normalizeQueryAcrossVersions(MongoClientTracer tracer, BsonDocument query) { def sanitizeStatementAcrossVersions(MongoClientTracer tracer, BsonDocument query) {
return normalizeAcrossVersions(tracer.normalizeQuery(query)) return sanitizeAcrossVersions(tracer.sanitizeStatement(query))
} }
def normalizeAcrossVersions(String json) { def sanitizeAcrossVersions(String json) {
json = json.replaceAll('\\{ ', '{') json = json.replaceAll('\\{ ', '{')
json = json.replaceAll(' }', '}') json = json.replaceAll(' }', '}')
json = json.replaceAll(' :', ':') json = json.replaceAll(' :', ':')

View File

@ -11,7 +11,7 @@ import java.net.InetSocketAddress;
import redis.RedisCommand; import redis.RedisCommand;
public class RediscalaClientTracer public class RediscalaClientTracer
extends DatabaseClientTracer<RedisCommand<?, ?>, RedisCommand<?, ?>> { extends DatabaseClientTracer<RedisCommand<?, ?>, RedisCommand<?, ?>, String> {
private static final RediscalaClientTracer TRACER = new RediscalaClientTracer(); private static final RediscalaClientTracer TRACER = new RediscalaClientTracer();
@ -20,20 +20,32 @@ public class RediscalaClientTracer
} }
@Override @Override
protected String normalizeQuery(RedisCommand redisCommand) { protected String sanitizeStatement(RedisCommand<?, ?> redisCommand) {
return spanNameForClass(redisCommand.getClass()); return spanNameForClass(redisCommand.getClass());
} }
@Override @Override
protected String dbSystem(RedisCommand redisCommand) { protected String spanName(
RedisCommand<?, ?> connection, RedisCommand<?, ?> statement, String operation) {
return operation;
}
@Override
protected String dbSystem(RedisCommand<?, ?> redisCommand) {
return DbSystemValues.REDIS; return DbSystemValues.REDIS;
} }
@Override @Override
protected InetSocketAddress peerAddress(RedisCommand redisCommand) { protected InetSocketAddress peerAddress(RedisCommand<?, ?> redisCommand) {
return null; return null;
} }
@Override
protected String dbStatement(
RedisCommand<?, ?> connection, RedisCommand<?, ?> command, String operation) {
return operation;
}
@Override @Override
protected String getInstrumentationName() { protected String getInstrumentationName() {
return "io.opentelemetry.javaagent.rediscala"; return "io.opentelemetry.javaagent.rediscala";

View File

@ -5,6 +5,9 @@
package io.opentelemetry.javaagent.instrumentation.redisson; package io.opentelemetry.javaagent.instrumentation.redisson;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
@ -13,11 +16,13 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValu
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.redisson.client.RedisConnection; import org.redisson.client.RedisConnection;
import org.redisson.client.protocol.CommandData; import org.redisson.client.protocol.CommandData;
import org.redisson.client.protocol.CommandsData; import org.redisson.client.protocol.CommandsData;
public class RedissonClientTracer extends DatabaseClientTracer<RedisConnection, Object> { public class RedissonClientTracer
extends DatabaseClientTracer<RedisConnection, Object, List<String>> {
private static final String UNKNOWN_COMMAND = "Redis Command"; private static final String UNKNOWN_COMMAND = "Redis Command";
private static final RedissonClientTracer TRACER = new RedissonClientTracer(); private static final RedissonClientTracer TRACER = new RedissonClientTracer();
@ -27,22 +32,24 @@ public class RedissonClientTracer extends DatabaseClientTracer<RedisConnection,
} }
@Override @Override
protected String spanName(RedisConnection connection, Object query, String normalizedQuery) { protected String spanName(
if (query instanceof CommandsData) { RedisConnection connection, Object ignored, List<String> sanitizedStatements) {
List<CommandData<?, ?>> commands = ((CommandsData) query).getCommands(); switch (sanitizedStatements.size()) {
StringBuilder commandStrings = new StringBuilder(); case 0:
for (CommandData<?, ?> commandData : commands) { return UNKNOWN_COMMAND;
commandStrings.append(commandData.getCommand().getName()).append(";"); // optimize for the most common case
case 1:
return getCommandName(sanitizedStatements.get(0));
default:
return sanitizedStatements.stream()
.map(this::getCommandName)
.collect(Collectors.joining(";"));
} }
if (commandStrings.length() > 0) {
commandStrings.deleteCharAt(commandStrings.length() - 1);
}
return commandStrings.toString();
} else if (query instanceof CommandData) {
return ((CommandData<?, ?>) query).getCommand().getName();
} }
return UNKNOWN_COMMAND; private String getCommandName(String statement) {
int spacePos = statement.indexOf(' ');
return spacePos == -1 ? statement : statement.substring(0, spacePos);
} }
@Override @Override
@ -51,22 +58,15 @@ public class RedissonClientTracer extends DatabaseClientTracer<RedisConnection,
} }
@Override @Override
protected String normalizeQuery(Object command) { protected List<String> sanitizeStatement(Object command) {
// get command // get command
if (command instanceof CommandsData) { if (command instanceof CommandsData) {
List<CommandData<?, ?>> commands = ((CommandsData) command).getCommands(); List<CommandData<?, ?>> commands = ((CommandsData) command).getCommands();
StringBuilder commandStrings = new StringBuilder(); return commands.stream().map(this::normalizeSingleCommand).collect(Collectors.toList());
for (CommandData<?, ?> commandData : commands) {
commandStrings.append(normalizeSingleCommand(commandData)).append(";");
}
if (commandStrings.length() > 0) {
commandStrings.deleteCharAt(commandStrings.length() - 1);
}
return commandStrings.toString();
} else if (command instanceof CommandData) { } else if (command instanceof CommandData) {
return normalizeSingleCommand((CommandData<?, ?>) command); return singletonList(normalizeSingleCommand((CommandData<?, ?>) command));
} }
return UNKNOWN_COMMAND; return emptyList();
} }
private String normalizeSingleCommand(CommandData<?, ?> command) { private String normalizeSingleCommand(CommandData<?, ?> command) {
@ -110,4 +110,18 @@ public class RedissonClientTracer extends DatabaseClientTracer<RedisConnection,
InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress(); InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
return remoteAddress.getHostString() + ":" + remoteAddress.getPort(); return remoteAddress.getHostString() + ":" + remoteAddress.getPort();
} }
@Override
protected String dbStatement(
RedisConnection connection, Object ignored, List<String> sanitizedStatements) {
switch (sanitizedStatements.size()) {
case 0:
return UNKNOWN_COMMAND;
// optimize for the most common case
case 1:
return sanitizedStatements.get(0);
default:
return String.join(";", sanitizedStatements);
}
}
} }

View File

@ -64,8 +64,12 @@ public abstract class CompletionListener<T> {
} }
protected void closeSyncSpan(Throwable thrown) { protected void closeSyncSpan(Throwable thrown) {
if (thrown == null) {
tracer().end(context);
} else {
tracer().endExceptionally(context, thrown); tracer().endExceptionally(context, thrown);
} }
}
protected abstract void processResult(Span span, T future) protected abstract void processResult(Span span, T future)
throws ExecutionException, InterruptedException; throws ExecutionException, InterruptedException;

View File

@ -5,19 +5,33 @@
package io.opentelemetry.javaagent.instrumentation.spymemcached; package io.opentelemetry.javaagent.instrumentation.spymemcached;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer; import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import net.spy.memcached.MemcachedConnection; import net.spy.memcached.MemcachedConnection;
public class MemcacheClientTracer extends DatabaseClientTracer<MemcachedConnection, String> { public class MemcacheClientTracer
extends DatabaseClientTracer<MemcachedConnection, String, String> {
private static final MemcacheClientTracer TRACER = new MemcacheClientTracer(); private static final MemcacheClientTracer TRACER = new MemcacheClientTracer();
public static MemcacheClientTracer tracer() { public static MemcacheClientTracer tracer() {
return TRACER; return TRACER;
} }
@Override
protected String sanitizeStatement(String methodName) {
char[] chars =
methodName
.replaceFirst("^async", "")
// 'CAS' name is special, we have to lowercase whole name
.replaceFirst("^CAS", "cas")
.toCharArray();
// Lowercase first letter
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
@Override @Override
protected String dbSystem(MemcachedConnection memcachedConnection) { protected String dbSystem(MemcachedConnection memcachedConnection) {
return "memcached"; return "memcached";
@ -29,23 +43,9 @@ public class MemcacheClientTracer extends DatabaseClientTracer<MemcachedConnecti
} }
@Override @Override
protected void onStatement(Span span, String statement) { protected String dbOperation(
span.setAttribute(SemanticAttributes.DB_OPERATION, statement); MemcachedConnection connection, String methodName, String operation) {
} return operation;
@Override
protected String normalizeQuery(String query) {
char[] chars =
query
.replaceFirst("^async", "")
// 'CAS' name is special, we have to lowercase whole name
.replaceFirst("^CAS", "cas")
.toCharArray();
// Lowercase first letter
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
} }
@Override @Override