Use low cardinality span names in Cassandra instrumentation
MODIFY
instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/DatabaseClientTracer.java
instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/tracer/DatabaseClientTracer.java
@@ -83,7 +83,7 @@ public abstract class DatabaseClientTracer<CONNECTION, STATEMENT, SANITIZEDSTATE
@Nullable String table,
String defaultValue) {
if (operation == null) {
- return dbName == null ? DB_QUERY : dbName;
+ return dbName == null ? defaultValue : dbName;
}
StringBuilder name = new StringBuilder(operation);

MODIFY
instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraDatabaseClientTracer.java
instrumentation/cassandra/cassandra-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v3_0/CassandraDatabaseClientTracer.java
@@ -18,6 +18,7 @@ import io.opentelemetry.javaagent.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues;
import java.net.InetSocketAddress;
+import org.checkerframework.checker.nullness.qual.Nullable;
public class CassandraDatabaseClientTracer
extends DatabaseClientTracer<Session, String, SqlStatementInfo> {
@@ -34,22 +35,27 @@ public class CassandraDatabaseClientTracer
@Override
protected SqlStatementInfo sanitizeStatement(String statement) {
- return SqlStatementSanitizer.sanitize(statement);
+ return SqlStatementSanitizer.sanitize(statement).mapTable(this::stripKeyspace);
}
- // 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;
+ // account for splitting out the keyspace, <keyspace>.<table>
+ @Nullable
+ private String stripKeyspace(String table) {
+ int i;
+ if (table == null || (i = table.indexOf('.')) == -1) {
+ return table;
}
-
- String result = null;
- if (connection != null) {
- result = dbName(connection);
+ return table.substring(i + 1);
}
- return result == null ? DB_QUERY : result;
+
+ @Override
+ protected String spanName(
+ Session connection, String statement, SqlStatementInfo sanitizedStatement) {
+ return conventionSpanName(
+ dbName(connection),
+ sanitizedStatement.getOperation(),
+ sanitizedStatement.getTable(),
+ sanitizedStatement.getFullStatement());
}
@Override
@@ -73,12 +79,28 @@ public class CassandraDatabaseClientTracer
return null;
}
+ @Override
+ protected void onStatement(
+ SpanBuilder span, Session connection, String statement, SqlStatementInfo sanitizedStatement) {
+ super.onStatement(span, connection, statement, sanitizedStatement);
+ String table = sanitizedStatement.getTable();
+ if (table != null) {
+ span.setAttribute(SemanticAttributes.DB_CASSANDRA_TABLE, table);
+ }
+ }
+
@Override
protected String dbStatement(
Session connection, String statement, SqlStatementInfo sanitizedStatement) {
return sanitizedStatement.getFullStatement();
}
+ @Override
+ protected String dbOperation(
+ Session connection, String statement, SqlStatementInfo sanitizedStatement) {
+ return sanitizedStatement.getOperation();
+ }
+
public void end(Context context, ExecutionInfo executionInfo) {
Span span = Span.fromContext(context);
Host host = executionInfo.getQueriedHost();

MODIFY
instrumentation/cassandra/cassandra-3.0/javaagent/src/test/groovy/CassandraClientTest.groovy
instrumentation/cassandra/cassandra-3.0/javaagent/src/test/groovy/CassandraClientTest.groovy
@@ -64,11 +64,11 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
assertTraces(keyspace ? 2 : 1) {
if (keyspace) {
trace(0, 1) {
- cassandraSpan(it, 0, "USE $keyspace", null)
+ cassandraSpan(it, 0, "USE $keyspace", "USE $keyspace")
}
}
trace(keyspace ? 1 : 0, 1) {
- cassandraSpan(it, 0, expectedStatement, keyspace)
+ cassandraSpan(it, 0, spanName, expectedStatement, operation, keyspace, table)
}
}
@@ -76,12 +76,12 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
session.close()
where:
- keyspace | statement | expectedStatement
- null | "DROP KEYSPACE IF EXISTS sync_test" | "DROP KEYSPACE IF EXISTS sync_test"
- null | "CREATE KEYSPACE sync_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | "CREATE KEYSPACE sync_test WITH REPLICATION = {?:?, ?:?}"
- "sync_test" | "CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )" | "CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )"
- "sync_test" | "INSERT INTO sync_test.users (id, name) values (uuid(), 'alice')" | "INSERT INTO sync_test.users (id, name) values (uuid(), ?)"
- "sync_test" | "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "SELECT * FROM users where name = ? ALLOW FILTERING"
+ keyspace | statement | expectedStatement | spanName | operation | table
+ null | "DROP KEYSPACE IF EXISTS sync_test" | "DROP KEYSPACE IF EXISTS sync_test" | expectedStatement | null | null
+ null | "CREATE KEYSPACE sync_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | "CREATE KEYSPACE sync_test WITH REPLICATION = {?:?, ?:?}" | expectedStatement | null | null
+ "sync_test" | "CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )" | "CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )" | "sync_test" | null | null
+ "sync_test" | "INSERT INTO sync_test.users (id, name) values (uuid(), 'alice')" | "INSERT INTO sync_test.users (id, name) values (uuid(), ?)" | "INSERT sync_test.users" | "INSERT" | "users"
+ "sync_test" | "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "SELECT * FROM users where name = ? ALLOW FILTERING" | "SELECT sync_test.users" | "SELECT" | "users"
}
def "test async"() {
@@ -101,12 +101,12 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
assertTraces(keyspace ? 2 : 1) {
if (keyspace) {
trace(0, 1) {
- cassandraSpan(it, 0, "USE $keyspace", null)
+ cassandraSpan(it, 0, "USE $keyspace", "USE $keyspace")
}
}
trace(keyspace ? 1 : 0, 3) {
basicSpan(it, 0, "parent")
- cassandraSpan(it, 1, expectedStatement, keyspace, span(0))
+ cassandraSpan(it, 1, spanName, expectedStatement, operation, keyspace, table, span(0))
basicSpan(it, 2, "callbackListener", span(0))
}
}
@@ -115,17 +115,21 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
session.close()
where:
- keyspace | statement | expectedStatement
- null | "DROP KEYSPACE IF EXISTS async_test" | "DROP KEYSPACE IF EXISTS async_test"
- null | "CREATE KEYSPACE async_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | "CREATE KEYSPACE async_test WITH REPLICATION = {?:?, ?:?}"
- "async_test" | "CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )" | "CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )"
- "async_test" | "INSERT INTO async_test.users (id, name) values (uuid(), 'alice')" | "INSERT INTO async_test.users (id, name) values (uuid(), ?)"
- "async_test" | "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "SELECT * FROM users where name = ? ALLOW FILTERING"
- }
-
- def cassandraSpan(TraceAssert trace, int index, String statement, String keyspace, Object parentSpan = null, Throwable exception = null) {
+ keyspace | statement | expectedStatement | spanName | operation | table
+ null | "DROP KEYSPACE IF EXISTS async_test" | "DROP KEYSPACE IF EXISTS async_test" | expectedStatement | null | null
+ null | "CREATE KEYSPACE async_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | "CREATE KEYSPACE async_test WITH REPLICATION = {?:?, ?:?}" | expectedStatement | null | null
+ "async_test" | "CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )" | "CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )" | "async_test" | null | null
+ "async_test" | "INSERT INTO async_test.users (id, name) values (uuid(), 'alice')" | "INSERT INTO async_test.users (id, name) values (uuid(), ?)" | "INSERT async_test.users" | "INSERT" | "users"
+ "async_test" | "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "SELECT * FROM users where name = ? ALLOW FILTERING" | "SELECT async_test.users" | "SELECT" | "users"
+ }
+
+ def cassandraSpan(TraceAssert trace, int index, String spanName, String statement,
+ String operation = null,
+ String keyspace = null,
+ String table = null,
+ Object parentSpan = null) {
trace.span(index) {
- name statement
+ name spanName
kind CLIENT
if (parentSpan == null) {
hasNoParent()
@@ -139,7 +143,9 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.DB_SYSTEM.key" "cassandra"
"$SemanticAttributes.DB_NAME.key" keyspace
"$SemanticAttributes.DB_STATEMENT.key" statement
+ "$SemanticAttributes.DB_OPERATION.key" operation
"$SemanticAttributes.DB_CASSANDRA_KEYSPACE.key" keyspace
+ "$SemanticAttributes.DB_CASSANDRA_TABLE.key" table
}
}
}

MODIFY
instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraDatabaseClientTracer.java
instrumentation/cassandra/cassandra-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/cassandra/v4_0/CassandraDatabaseClientTracer.java
@@ -24,6 +24,7 @@ import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.DbSystemValues;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
+import org.checkerframework.checker.nullness.qual.Nullable;
public class CassandraDatabaseClientTracer
extends DatabaseClientTracer<CqlSession, String, SqlStatementInfo> {
@@ -41,22 +42,27 @@ public class CassandraDatabaseClientTracer
@Override
protected SqlStatementInfo sanitizeStatement(String statement) {
- return SqlStatementSanitizer.sanitize(statement);
+ return SqlStatementSanitizer.sanitize(statement).mapTable(this::stripKeyspace);
}
- // 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;
+ // account for splitting out the keyspace, <keyspace>.<table>
+ @Nullable
+ private String stripKeyspace(String table) {
+ int i;
+ if (table == null || (i = table.indexOf('.')) == -1) {
+ return table;
}
-
- String result = null;
- if (connection != null) {
- result = dbName(connection);
+ return table.substring(i + 1);
}
- return result == null ? DB_QUERY : result;
+
+ @Override
+ protected String spanName(
+ CqlSession connection, String statement, SqlStatementInfo sanitizedStatement) {
+ return conventionSpanName(
+ dbName(connection),
+ sanitizedStatement.getOperation(),
+ sanitizedStatement.getTable(),
+ sanitizedStatement.getFullStatement());
}
@Override
@@ -74,6 +80,31 @@ public class CassandraDatabaseClientTracer
return null;
}
+ @Override
+ protected void onStatement(
+ SpanBuilder span,
+ CqlSession connection,
+ String statement,
+ SqlStatementInfo sanitizedStatement) {
+ super.onStatement(span, connection, statement, sanitizedStatement);
+ String table = sanitizedStatement.getTable();
+ if (table != null) {
+ span.setAttribute(SemanticAttributes.DB_CASSANDRA_TABLE, table);
+ }
+ }
+
+ @Override
+ protected String dbStatement(
+ CqlSession connection, String statement, SqlStatementInfo sanitizedStatement) {
+ return sanitizedStatement.getFullStatement();
+ }
+
+ @Override
+ protected String dbOperation(
+ CqlSession connection, String statement, SqlStatementInfo sanitizedStatement) {
+ return sanitizedStatement.getOperation();
+ }
+
public void onResponse(Context context, CqlSession cqlSession, ExecutionInfo executionInfo) {
Span span = Span.fromContext(context);
Node coordinator = executionInfo.getCoordinator();
@@ -143,28 +174,4 @@ public class CassandraDatabaseClientTracer
throw new IllegalStateException(
"use the endExceptionally method with a CqlSession in CassandraDatabaseClientTracer");
}
-
- @Override
- protected void onStatement(
- SpanBuilder span,
- CqlSession connection,
- String statement,
- SqlStatementInfo sanitizedStatement) {
- super.onStatement(span, connection, statement, sanitizedStatement);
- String table = sanitizedStatement.getTable();
- if (table != null) {
- // account for splitting out the keyspace, <keyspace>.<table>
- int i = table.indexOf('.');
- if (i > -1 && i + 1 < table.length()) {
- table = table.substring(i + 1);
- }
- span.setAttribute(SemanticAttributes.DB_CASSANDRA_TABLE, table);
- }
- }
-
- @Override
- protected String dbStatement(
- CqlSession connection, String statement, SqlStatementInfo sanitizedStatement) {
- return sanitizedStatement.getFullStatement();
- }
}

MODIFY
instrumentation/cassandra/cassandra-4.0/javaagent/src/test/groovy/CassandraClientTest.groovy
instrumentation/cassandra/cassandra-4.0/javaagent/src/test/groovy/CassandraClientTest.groovy
@@ -53,7 +53,7 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
expect:
assertTraces(1) {
trace(0, 1) {
- cassandraSpan(it, 0, expectedStatement, keyspace)
+ cassandraSpan(it, 0, spanName, expectedStatement, operation, keyspace, table)
}
}
@@ -61,12 +61,12 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
session.close()
where:
- keyspace | statement | expectedStatement
- null | "DROP KEYSPACE IF EXISTS sync_test" | "DROP KEYSPACE IF EXISTS sync_test"
- null | "CREATE KEYSPACE sync_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | "CREATE KEYSPACE sync_test WITH REPLICATION = {?:?, ?:?}"
- "sync_test" | "CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )" | "CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )"
- "sync_test" | "INSERT INTO sync_test.users (id, name) values (uuid(), 'alice')" | "INSERT INTO sync_test.users (id, name) values (uuid(), ?)"
- "sync_test" | "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "SELECT * FROM users where name = ? ALLOW FILTERING"
+ keyspace | statement | expectedStatement | spanName | operation | table
+ null | "DROP KEYSPACE IF EXISTS sync_test" | "DROP KEYSPACE IF EXISTS sync_test" | expectedStatement | null | null
+ null | "CREATE KEYSPACE sync_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | "CREATE KEYSPACE sync_test WITH REPLICATION = {?:?, ?:?}" | expectedStatement | null | null
+ "sync_test" | "CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )" | "CREATE TABLE sync_test.users ( id UUID PRIMARY KEY, name text )" | "sync_test" | null | null
+ "sync_test" | "INSERT INTO sync_test.users (id, name) values (uuid(), 'alice')" | "INSERT INTO sync_test.users (id, name) values (uuid(), ?)" | "INSERT sync_test.users" | "INSERT" | "users"
+ "sync_test" | "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "SELECT * FROM users where name = ? ALLOW FILTERING" | "SELECT sync_test.users" | "SELECT" | "users"
}
def "test async"() {
@@ -81,7 +81,7 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent")
- cassandraSpan(it, 1, expectedStatement, keyspace, span(0))
+ cassandraSpan(it, 1, spanName, expectedStatement, operation, keyspace, table, span(0))
}
}
@@ -89,17 +89,17 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
session.close()
where:
- keyspace | statement | expectedStatement
- null | "DROP KEYSPACE IF EXISTS async_test" | "DROP KEYSPACE IF EXISTS async_test"
- null | "CREATE KEYSPACE async_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | "CREATE KEYSPACE async_test WITH REPLICATION = {?:?, ?:?}"
- "async_test" | "CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )" | "CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )"
- "async_test" | "INSERT INTO async_test.users (id, name) values (uuid(), 'alice')" | "INSERT INTO async_test.users (id, name) values (uuid(), ?)"
- "async_test" | "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "SELECT * FROM users where name = ? ALLOW FILTERING"
+ keyspace | statement | expectedStatement | spanName | operation | table
+ null | "DROP KEYSPACE IF EXISTS async_test" | "DROP KEYSPACE IF EXISTS async_test" | expectedStatement | null | null
+ null | "CREATE KEYSPACE async_test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':3}" | "CREATE KEYSPACE async_test WITH REPLICATION = {?:?, ?:?}" | expectedStatement | null | null
+ "async_test" | "CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )" | "CREATE TABLE async_test.users ( id UUID PRIMARY KEY, name text )" | "async_test" | null | null
+ "async_test" | "INSERT INTO async_test.users (id, name) values (uuid(), 'alice')" | "INSERT INTO async_test.users (id, name) values (uuid(), ?)" | "INSERT async_test.users" | "INSERT" | "users"
+ "async_test" | "SELECT * FROM users where name = 'alice' ALLOW FILTERING" | "SELECT * FROM users where name = ? ALLOW FILTERING" | "SELECT async_test.users" | "SELECT" | "users"
}
- def cassandraSpan(TraceAssert trace, int index, String statement, String keyspace, Object parentSpan = null, Throwable exception = null) {
+ def cassandraSpan(TraceAssert trace, int index, String spanName, String statement, String operation, String keyspace, String table, Object parentSpan = null) {
trace.span(index) {
- name statement
+ name spanName
kind CLIENT
if (parentSpan == null) {
hasNoParent()
@@ -113,6 +113,7 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.DB_SYSTEM.key" "cassandra"
"$SemanticAttributes.DB_NAME.key" keyspace
"$SemanticAttributes.DB_STATEMENT.key" statement
+ "$SemanticAttributes.DB_OPERATION.key" operation
"$SemanticAttributes.DB_CASSANDRA_CONSISTENCY_LEVEL.key" "LOCAL_ONE"
"$SemanticAttributes.DB_CASSANDRA_COORDINATOR_DC.key" "datacenter1"
"$SemanticAttributes.DB_CASSANDRA_COORDINATOR_ID.key" String
@@ -120,7 +121,7 @@ class CassandraClientTest extends AgentInstrumentationSpecification {
"$SemanticAttributes.DB_CASSANDRA_PAGE_SIZE.key" 5000
"$SemanticAttributes.DB_CASSANDRA_SPECULATIVE_EXECUTION_COUNT.key" 0
// the SqlStatementSanitizer can't handle CREATE statements yet
- "$SemanticAttributes.DB_CASSANDRA_TABLE.key" { table -> (statement.contains("CREATE") || keyspace == null ? true : table == "users") }
+ "$SemanticAttributes.DB_CASSANDRA_TABLE.key" table
}
}
}

MODIFY
javaagent-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/db/SqlStatementInfo.java
javaagent-api/src/main/java/io/opentelemetry/javaagent/instrumentation/api/db/SqlStatementInfo.java
package io.opentelemetry.javaagent.instrumentation.api.db;
import java.util.Objects;
+import java.util.function.Function;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class SqlStatementInfo {
@@ -20,6 +21,10 @@ public final class SqlStatementInfo {
this.table = table;
}
+ public SqlStatementInfo mapTable(Function<String, String> mapper) {
+ return new SqlStatementInfo(fullStatement, operation, mapper.apply(table));
+ }
+
@Nullable
public String getFullStatement() {
return fullStatement;

Use low cardinality span names in Cassandra instrumentation (#2423)