diff --git a/.build/build-resolver.xml b/.build/build-resolver.xml
index 7e88a4ee7334..7a4cf947ac8b 100644
--- a/.build/build-resolver.xml
+++ b/.build/build-resolver.xml
@@ -185,7 +185,7 @@
-
+
@@ -212,7 +212,7 @@
-
+
diff --git a/.build/cassandra-build-maven-pom.xml b/.build/cassandra-build-maven-pom.xml
index bf2adfd9f8e4..6afc44108ca9 100644
--- a/.build/cassandra-build-maven-pom.xml
+++ b/.build/cassandra-build-maven-pom.xml
@@ -164,5 +164,9 @@
cassandra-accord
tests
+
+ io.opentelemetry
+ opentelemetry-sdk-testing
+
diff --git a/.build/cassandra-deps-maven-pom.xml b/.build/cassandra-deps-maven-pom.xml
index 474c1037d0b1..9acf26ffb22e 100644
--- a/.build/cassandra-deps-maven-pom.xml
+++ b/.build/cassandra-deps-maven-pom.xml
@@ -388,5 +388,26 @@
org.passay
passay
+
+ io.opentelemetry
+ opentelemetry-api
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+
+
+ io.opentelemetry
+ opentelemetry-sdk-extension-autoconfigure
+
+
+ io.opentelemetry
+ opentelemetry-exporter-otlp
+ runtime
+
+
+ io.opentelemetry.semconv
+ opentelemetry-semconv
+
diff --git a/.build/parent-maven-pom.xml b/.build/parent-maven-pom.xml
index e8d82bc4edf4..33d4928a9197 100644
--- a/.build/parent-maven-pom.xml
+++ b/.build/parent-maven-pom.xml
@@ -1316,6 +1316,18 @@
passay
1.6.4
+
+ io.opentelemetry
+ opentelemetry-bom
+ 1.62.0
+ pom
+ import
+
+
+ io.opentelemetry.semconv
+ opentelemetry-semconv
+ 1.41.1
+
diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml
index 422ad0a482ad..d589ee69b61d 100644
--- a/conf/cassandra.yaml
+++ b/conf/cassandra.yaml
@@ -2999,3 +2999,9 @@ compression_dictionary_cache_size: 10
# Expired dictionaries will be removed from memory but can be reloaded if needed.
# Min unit: s
compression_dictionary_cache_expire: 24h
+
+# OpenTelemetry integration
+# If enabled, Cassandra can export telemetry using OpenTelemetry.
+# Currently, only tracing is exported
+# Default: false
+opentelemetry_enabled: false
diff --git a/conf/cassandra_latest.yaml b/conf/cassandra_latest.yaml
index ee68c700827f..af356c8029cb 100644
--- a/conf/cassandra_latest.yaml
+++ b/conf/cassandra_latest.yaml
@@ -2742,3 +2742,9 @@ compression_dictionary_cache_size: 10
# Expired dictionaries will be removed from memory but can be reloaded if needed.
# Min unit: s
compression_dictionary_cache_expire: 24h
+
+# OpenTelemetry integration
+# If enabled, Cassandra can export telemetry using OpenTelemetry.
+# Currently, only tracing is exported
+# Default: false
+opentelemetry_enabled: false
diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java
index 501d1284d2e0..f1c2074cde36 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -1041,6 +1041,8 @@ public static void setClientMode(boolean clientMode)
public volatile CustomGuardrailConfig role_name_policy = new CustomGuardrailConfig();
public volatile AutoRepairConfig auto_repair = new AutoRepairConfig();
+ public volatile boolean opentelemetry_enabled = false;
+
/**
* The variants of paxos implementation and semantics supported by Cassandra.
*/
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index 9ee09db70709..081370df883a 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -6255,6 +6255,11 @@ public static void setRepairDiskHeadroomRejectRatio(double value)
conf.repair_disk_headroom_reject_ratio = value;
}
+ public static boolean getOpenTelemetryEnabled()
+ {
+ return conf.opentelemetry_enabled;
+ }
+
@VisibleForTesting
public static void setPartitioner(String name)
{
diff --git a/src/java/org/apache/cassandra/cql3/CQLStatement.java b/src/java/org/apache/cassandra/cql3/CQLStatement.java
index 25c42136692e..6597c737d714 100644
--- a/src/java/org/apache/cassandra/cql3/CQLStatement.java
+++ b/src/java/org/apache/cassandra/cql3/CQLStatement.java
@@ -129,6 +129,17 @@ default boolean eligibleAsPreparedStatement()
return false;
}
+ /**
+ * The query summary describes a class of database queries and is useful as a grouping key.
+ * This is used in telemetry to group similar queries together.
+ *
+ * @see Generating a summary of the query
+ */
+ default String getQuerySummary()
+ {
+ return "";
+ }
+
abstract class Raw
{
protected VariableSpecifications bindVariables;
diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
index 518cd694ba4d..05643831920c 100644
--- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java
@@ -28,6 +28,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
@@ -818,4 +819,19 @@ public AuditLogContext getAuditLogContext()
{
return new AuditLogContext(AuditLogEntryType.BATCH);
}
+
+ /**
+ * For batch statement, the query summary consists of deduplicated list of targets
+ * not to produce too long query summary.
+ */
+ @Override
+ public String getQuerySummary()
+ {
+ TreeSet summary = new TreeSet<>();
+ for (ModificationStatement statement : statements)
+ {
+ summary.add(statement.getQuerySummary());
+ }
+ return String.format("%s BATCH %s", type.name(), String.join(" ", summary));
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java
index dcd1395ab8df..58f88859ab2f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java
@@ -188,6 +188,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_ROLE);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return "CREATE ROLE";
+ }
+
@Override
public String obfuscatePassword(String query)
{
diff --git a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java
index 5f47063809c3..ba2ff266a5a1 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java
@@ -143,6 +143,12 @@ public final AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.DESCRIBE);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return "DESCRIBE";
+ }
+
@Override
public final ResultMessage execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestValidationException, RequestExecutionException
{
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropIdentityStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropIdentityStatement.java
index cc43b4e2e3c1..2c83dbe500f4 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropIdentityStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropIdentityStatement.java
@@ -89,6 +89,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.DROP_IDENTITY);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return "DROP IDENTITY";
+ }
+
@Override
public ResultMessage execute(ClientState state) throws RequestExecutionException, RequestValidationException
{
diff --git a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
index 7a7b78da80d2..4ce17fbfd6c6 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java
@@ -97,4 +97,10 @@ public AuditLogContext getAuditLogContext()
{
return new AuditLogContext(AuditLogEntryType.DROP_ROLE);
}
+
+ @Override
+ public String getQuerySummary()
+ {
+ return "DROP ROLE";
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java b/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java
index eb581c3c96a9..becc66ec40ef 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java
@@ -156,4 +156,10 @@ public AuditLogContext getAuditLogContext()
{
return new AuditLogContext(AuditLogEntryType.LIST_PERMISSIONS);
}
+
+ @Override
+ public String getQuerySummary()
+ {
+ return "LIST PERMISSIONS";
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/ListRolesStatement.java b/src/java/org/apache/cassandra/cql3/statements/ListRolesStatement.java
index 3a592fec6403..7a6f59f04f45 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ListRolesStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ListRolesStatement.java
@@ -155,4 +155,10 @@ public AuditLogContext getAuditLogContext()
{
return new AuditLogContext(AuditLogEntryType.LIST_ROLES);
}
+
+ @Override
+ public String getQuerySummary()
+ {
+ return "LIST ROLES";
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/ListSuperUsersStatement.java b/src/java/org/apache/cassandra/cql3/statements/ListSuperUsersStatement.java
index 3c94a14195f0..19ba5cd9099c 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ListSuperUsersStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ListSuperUsersStatement.java
@@ -99,4 +99,10 @@ public AuditLogContext getAuditLogContext()
{
return new AuditLogContext(AuditLogEntryType.LIST_SUPERUSERS);
}
+
+ @Override
+ public String getQuerySummary()
+ {
+ return "LIST SUPERUSERS";
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
index f7d9fbc9a3ca..19e7e57fb4c6 100644
--- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java
@@ -311,6 +311,12 @@ public boolean eligibleAsPreparedStatement()
return true;
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("%s %s", type.name(), metadata.toString());
+ }
+
public void addFunctionsTo(List functions)
{
attrs.addFunctionsTo(functions);
diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
index 7e8119a2e395..5913e06d91ae 100644
--- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java
@@ -534,6 +534,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.SELECT, keyspace(), table.name);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("SELECT %s", table.toString());
+ }
+
// Simple wrapper class to avoid some code duplication
private static abstract class Pager
{
diff --git a/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java b/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java
index 8799ffc97144..2cd4512c6588 100644
--- a/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java
@@ -134,4 +134,10 @@ public AuditLogContext getAuditLogContext()
{
return new AuditLogContext(AuditLogEntryType.TRUNCATE, keyspace(), name());
}
+
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("TRUNCATE %s", qualifiedName);
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/UseStatement.java b/src/java/org/apache/cassandra/cql3/statements/UseStatement.java
index 319e14899035..e7d73ed947ec 100644
--- a/src/java/org/apache/cassandra/cql3/statements/UseStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/UseStatement.java
@@ -87,6 +87,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.USE_KEYSPACE, keyspace);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("USE %s", keyspace);
+ }
+
public String keyspace()
{
return keyspace;
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
index 5ef2b68a9c09..0512b1f6ab7d 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
@@ -232,6 +232,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.ALTER_KEYSPACE, keyspaceName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("ALTER KEYSPACE %s", keyspaceName);
+ }
+
public String toString()
{
return String.format("%s (%s)", getClass().getSimpleName(), keyspaceName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
index aee1496cb5f9..6287e81875de 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
@@ -156,6 +156,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.ALTER_TABLE, keyspaceName, tableName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("ALTER TABLE %s.%s", keyspaceName, tableName);
+ }
+
public String toString()
{
return format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, tableName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
index dd619af7a4cf..5177518a575e 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
@@ -101,6 +101,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.ALTER_TYPE, keyspaceName, typeName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("ALTER TYPE %s.%s", keyspaceName, typeName);
+ }
+
public String toString()
{
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, typeName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
index 0bce6f6f30df..3b2c6817edc3 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
@@ -126,6 +126,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.ALTER_VIEW, keyspaceName, viewName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("ALTER VIEW %s.%s", keyspaceName, viewName);
+ }
+
public String toString()
{
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, viewName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java
index 1250cb79ec6b..6f277ae8c73f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java
@@ -112,6 +112,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_TABLE_LIKE, targetKeyspace, targetTableName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("CREATE TABLE %s.%s LIKE %s.%s", targetKeyspace, targetTableName, sourceKeyspace, sourceTableName);
+ }
+
@Override
public boolean compatibleWith(ClusterMetadata metadata)
{
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
index eaddab2af7f9..19cb2598095a 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
@@ -297,6 +297,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_AGGREGATE, keyspaceName, aggregateName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("CREATE AGGREGATE %s.%s", keyspaceName, aggregateName);
+ }
+
public String toString()
{
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, aggregateName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
index 9505d7f43606..d19866779a38 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
@@ -210,6 +210,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_FUNCTION, keyspaceName, functionName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("CREATE FUNCTION %s.%s", keyspaceName, functionName);
+ }
+
public String toString()
{
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, functionName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
index d3e08dfee5c7..018af58916bd 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
@@ -347,6 +347,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_INDEX, keyspaceName, indexName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("CREATE INDEX %s.%s", keyspaceName, indexName);
+ }
+
public String toString()
{
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, indexName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
index a622b861921c..2e67fe948ea8 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
@@ -136,6 +136,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_KEYSPACE, keyspaceName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("CREATE KEYSPACE %s", keyspaceName);
+ }
+
public String toString()
{
return String.format("%s (%s)", getClass().getSimpleName(), keyspaceName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
index 26dfbb8dec76..f5886d4a5541 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
@@ -256,6 +256,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_TABLE, keyspaceName, tableName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("CREATE TABLE %s.%s", keyspaceName, tableName);
+ }
+
public String toString()
{
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, tableName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
index fcf23af3e0ef..b0d1b5200039 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
@@ -129,6 +129,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_TRIGGER, keyspaceName, triggerName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("CREATE TRIGGER %s.%s", keyspaceName, triggerName);
+ }
+
public String toString()
{
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, triggerName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
index 38d28443b6f4..5fb73578dd73 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
@@ -140,6 +140,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_TYPE, keyspaceName, typeName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("CREATE TYPE %s.%s", keyspaceName, typeName);
+ }
+
public String toString()
{
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, typeName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
index d0b219ca62cc..70d6d2f46cec 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
@@ -418,6 +418,12 @@ public AuditLogContext getAuditLogContext()
return new AuditLogContext(AuditLogEntryType.CREATE_VIEW, keyspaceName, viewName);
}
+ @Override
+ public String getQuerySummary()
+ {
+ return String.format("CREATE VIEW %s.%s", keyspaceName, viewName);
+ }
+
public String toString()
{
return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, viewName);
diff --git a/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java b/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java
index d0897afdd5af..e456a1bf7032 100644
--- a/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java
+++ b/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java
@@ -17,6 +17,8 @@
*/
package org.apache.cassandra.db;
+import java.util.stream.Collectors;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,6 +29,8 @@
import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.transport.Dispatcher;
+import io.opentelemetry.api.trace.Span;
+
public class CounterMutationVerbHandler extends AbstractMutationVerbHandler
{
public static final CounterMutationVerbHandler instance = new CounterMutationVerbHandler();
@@ -36,6 +40,12 @@ public class CounterMutationVerbHandler extends AbstractMutationVerbHandler message, InetAddressAndPort respondToAddress)
{
final CounterMutation cm = message.payload;
+ if (Span.current().getSpanContext().isValid())
+ {
+ String target = cm.getPartitionUpdates().stream()
+ .map((pu) -> pu.metadata().toString()).collect(Collectors.joining(" "));
+ Span.current().updateName(String.format("%s %s", message.verb().name(), target));
+ }
logger.trace("Applying forwarded {}", cm);
String localDataCenter = DatabaseDescriptor.getLocator().local().datacenter;
diff --git a/src/java/org/apache/cassandra/db/MutationVerbHandler.java b/src/java/org/apache/cassandra/db/MutationVerbHandler.java
index f4244c84e1b1..e247b7382727 100644
--- a/src/java/org/apache/cassandra/db/MutationVerbHandler.java
+++ b/src/java/org/apache/cassandra/db/MutationVerbHandler.java
@@ -18,6 +18,7 @@
package org.apache.cassandra.db;
import java.util.Map;
+import java.util.stream.Collectors;
import org.apache.cassandra.exceptions.WriteTimeoutException;
import org.apache.cassandra.locator.InetAddressAndPort;
@@ -27,6 +28,8 @@
import org.apache.cassandra.net.ParamType;
import org.apache.cassandra.tracing.Tracing;
+import io.opentelemetry.api.trace.Span;
+
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.db.commitlog.CommitLogSegment.ENTRY_OVERHEAD_SIZE;
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
@@ -58,6 +61,13 @@ public void doVerb(Message message)
return;
}
+ if (Span.current().getSpanContext().isValid())
+ {
+ String target = message.payload.getPartitionUpdates().stream()
+ .map((pu) -> pu.metadata().toString()).collect(Collectors.joining(" "));
+ Span.current().updateName(String.format("%s %s", message.verb().name(), target));
+ }
+
MessageParams.reset();
message.payload.validateSize(MessagingService.current_version, ENTRY_OVERHEAD_SIZE);
WriteThresholds.checkWriteThresholds(message.payload);
diff --git a/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java b/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java
index b418ecdfeb2a..c0a8034f3cde 100644
--- a/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java
+++ b/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java
@@ -44,6 +44,8 @@
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.FBUtilities;
+import io.opentelemetry.api.trace.Span;
+
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.exceptions.RequestFailureReason.RETRY_ON_DIFFERENT_TRANSACTION_SYSTEM;
@@ -67,6 +69,10 @@ public ReadResponse doRead(ReadCommand command, boolean trackRepairedData)
public void doVerb(Message message)
{
+ if (Span.current().getSpanContext().isValid())
+ {
+ Span.current().updateName(String.format("%s %s", message.verb().name(), message.payload.metadata().toString()));
+ }
if (message.epoch().isAfter(Epoch.EMPTY))
{
ClusterMetadata metadata = ClusterMetadata.current();
diff --git a/src/java/org/apache/cassandra/net/InboundMessageHandler.java b/src/java/org/apache/cassandra/net/InboundMessageHandler.java
index 3e0d40675940..e129e71de6ae 100644
--- a/src/java/org/apache/cassandra/net/InboundMessageHandler.java
+++ b/src/java/org/apache/cassandra/net/InboundMessageHandler.java
@@ -37,6 +37,8 @@
import org.apache.cassandra.net.ResourceLimits.Limit;
import org.apache.cassandra.service.accord.debug.AccordRemoteTracing;
import org.apache.cassandra.tcm.ClusterMetadataService;
+import org.apache.cassandra.telemetry.CassandraAttributes;
+import org.apache.cassandra.telemetry.Telemetry;
import org.apache.cassandra.tracing.TraceState;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.ByteBufferUtil;
@@ -46,6 +48,10 @@
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
@@ -421,12 +427,41 @@ private void dispatch(ProcessMessage task)
{
Header header = task.header();
+ // Start tracing
+ Context otelContext = header.traceContext();
+ final Span span;
+ // Only start replica span if the context was propagated
+ SpanContext propagatedSpanContext = Span.fromContext(otelContext).getSpanContext();
+ if (propagatedSpanContext.isValid() && propagatedSpanContext.isRemote())
+ {
+ span = Telemetry.getRequestTracer()
+ .spanBuilder(header.verb.name()) // Span name will be updated with additional details
+ .setParent(otelContext)
+ .setAttribute(CassandraAttributes.CASSANDRA_NET_VERB, header.verb.name())
+ .setAttribute(CassandraAttributes.THREAD_ID, Thread.currentThread().getId())
+ .setAttribute(CassandraAttributes.THREAD_NAME, Thread.currentThread().getName())
+ .startSpan();
+ }
+ else
+ {
+ span = Span.getInvalid();
+ }
+ // Legacy tracing
TraceState state = Tracing.instance.initializeFromMessage(header);
if (state != null) state.trace("{} message received from {}", header.verb, header.from);
AccordRemoteTracing.traceOffWire(header);
callbacks.onDispatched(task.size(), header);
- header.verb.stage.execute(ExecutorLocals.create(state), task);
+ header.verb.stage.execute(ExecutorLocals.create(state), () -> {
+ try (Scope scope = span.makeCurrent())
+ {
+ task.run();
+ }
+ finally
+ {
+ span.end();
+ }
+ });
}
private abstract class ProcessMessage implements Runnable
diff --git a/src/java/org/apache/cassandra/net/Message.java b/src/java/org/apache/cassandra/net/Message.java
index 41f0281706a8..163a1f86cad2 100644
--- a/src/java/org/apache/cassandra/net/Message.java
+++ b/src/java/org/apache/cassandra/net/Message.java
@@ -53,6 +53,9 @@
import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.TimeUUID;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Context;
+
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.db.TypeSizes.sizeof;
@@ -293,7 +296,7 @@ private static Message withParam(long id, Verb verb, long expiresAtNanos,
flags = ARTIFICIAL_LATENCY.addTo(flags);
InetAddressAndPort from = getBroadcastAddressAndPort();
- return new Message<>(new Header(id, epochSupplier.get(), verb, from, createdAtNanos, expiresAtNanos, flags, buildParams(paramType, paramValue)), payload);
+ return new Message<>(new Header(id, epochSupplier.get(), verb, from, createdAtNanos, expiresAtNanos, flags, buildParams(paramType, paramValue, verb.isResponse())), payload);
}
public static Message internalResponse(Verb verb, T payload)
@@ -428,7 +431,7 @@ public Message withParams(Map values)
private static final EnumMap NO_PARAMS = new EnumMap<>(ParamType.class);
- private static Map buildParams(ParamType type, Object value)
+ private static Map buildParams(ParamType type, Object value, boolean isResponse)
{
EnumMap params = NO_PARAMS;
if (Tracing.isTracing())
@@ -440,6 +443,13 @@ private static Map buildParams(ParamType type, Object value)
params = new EnumMap<>(ParamType.class);
params.put(type, value);
}
+ // OpenTelemetry tracing
+ if (!isResponse && Span.current().isRecording())
+ {
+ if (params.isEmpty())
+ params = new EnumMap<>(ParamType.class);
+ params.put(ParamType.TRACE_CONTEXT, Context.current());
+ }
return params;
}
@@ -645,6 +655,14 @@ public TraceType traceType()
return (TraceType) params.getOrDefault(ParamType.TRACE_TYPE, TraceType.QUERY);
}
+ /**
+ * @return the OpenTelemetry context when it is propagated from the remote peer.
+ */
+ public Context traceContext()
+ {
+ return (Context) params.getOrDefault(ParamType.TRACE_CONTEXT, Context.current());
+ }
+
public Map params()
{
return Collections.unmodifiableMap(params);
diff --git a/src/java/org/apache/cassandra/net/ParamType.java b/src/java/org/apache/cassandra/net/ParamType.java
index b7f666617e10..638df6ad20d3 100644
--- a/src/java/org/apache/cassandra/net/ParamType.java
+++ b/src/java/org/apache/cassandra/net/ParamType.java
@@ -22,6 +22,7 @@
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.service.accord.debug.AccordRemoteTracing;
import org.apache.cassandra.service.writes.thresholds.WriteThresholdMapSerializer;
+import org.apache.cassandra.telemetry.tracing.TraceContextSerializer;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.Int32Serializer;
import org.apache.cassandra.utils.Int64Serializer;
@@ -60,6 +61,7 @@ public enum ParamType
WRITE_SIZE_WARN (18, WriteThresholdMapSerializer.serializer),
WRITE_TOMBSTONE_WARN (19, WriteThresholdMapSerializer.serializer),
ACCORD_TRACING (20, AccordRemoteTracing.tracingSerializer),
+ TRACE_CONTEXT (21, TraceContextSerializer.serializer),
;
final int id;
diff --git a/src/java/org/apache/cassandra/service/StorageProxy.java b/src/java/org/apache/cassandra/service/StorageProxy.java
index 70de57d6be1a..0112b84a1597 100644
--- a/src/java/org/apache/cassandra/service/StorageProxy.java
+++ b/src/java/org/apache/cassandra/service/StorageProxy.java
@@ -177,6 +177,8 @@
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.membership.NodeState;
import org.apache.cassandra.tcm.ownership.VersionedEndpoints;
+import org.apache.cassandra.telemetry.CassandraAttributes;
+import org.apache.cassandra.telemetry.Telemetry;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.triggers.TriggerExecutor;
@@ -192,6 +194,11 @@
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+
import static accord.primitives.Txn.Kind.Read;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.concat;
@@ -304,8 +311,29 @@ private StorageProxy()
{
EndpointsForToken selected = targets.contacts().withoutSelf();
Replicas.temporaryAssertFull(selected); // TODO CASSANDRA-14548
- Stage.COUNTER_MUTATION.executor()
- .execute(counterWriteTask(mutation, targets.withContacts(selected), responseHandler, localDataCenter, requestTime));
+ Runnable task = counterWriteTask(mutation, targets.withContacts(selected), responseHandler, localDataCenter, requestTime);
+ Context context = Context.current();
+ Stage.COUNTER_MUTATION.execute(() -> {
+ // Create additional span on coordinator counter-mutation to distinguish from the coordinator span
+ Span parentSpan = Span.fromContext(context);
+ String target = mutation.getPartitionUpdates().stream().map((pu) -> pu.metadata().toString()).collect(Collectors.joining(" "));
+ Span counterMutationSpan = parentSpan.getSpanContext().isValid() ? Telemetry.getRequestTracer()
+ .spanBuilder(String.format("%s %s", Verb.COUNTER_MUTATION_REQ.name(), target))
+ .setParent(context)
+ .setAttribute(CassandraAttributes.THREAD_ID, Thread.currentThread().getId())
+ .setAttribute(CassandraAttributes.THREAD_NAME, Thread.currentThread().getName())
+ .startSpan()
+ : Span.getInvalid();
+
+ try (Scope ignore = counterMutationSpan.makeCurrent())
+ {
+ task.run();
+ }
+ finally
+ {
+ counterMutationSpan.end();
+ }
+ });
};
@@ -930,7 +958,7 @@ private static void commitPaxos(Commit proposal, ConsistencyLevel consistencyLev
*/
private static void commitPaxosLocal(Replica localReplica, final Message message, final AbstractWriteResponseHandler> responseHandler, Dispatcher.RequestTime requestTime)
{
- PAXOS_COMMIT_REQ.stage.maybeExecuteImmediately(new LocalMutationRunnable(localReplica, requestTime)
+ PAXOS_COMMIT_REQ.stage.maybeExecuteImmediately(Context.current().wrap(new LocalMutationRunnable(localReplica, requestTime)
{
public void runMayThrow()
{
@@ -959,7 +987,7 @@ protected Verb verb()
{
return PAXOS_COMMIT_REQ;
}
- });
+ }));
}
/**
@@ -1989,10 +2017,15 @@ private static Replica pickReplica(EndpointsForToken targets)
private static void performLocally(Stage stage, Replica localReplica, final Runnable runnable, String description, Dispatcher.RequestTime requestTime)
{
- stage.maybeExecuteImmediately(new LocalMutationRunnable(localReplica, requestTime)
+ stage.maybeExecuteImmediately(Context.current().wrap(new LocalMutationRunnable(localReplica, requestTime)
{
public void runMayThrow()
{
+ // update span name if keyspace and table name are available
+ if (Span.current().getSpanContext().isValid())
+ {
+ Span.current().updateName(String.format("%s %s", verb().name(), description));
+ }
try
{
runnable.run();
@@ -2014,15 +2047,31 @@ protected Verb verb()
{
return Verb.MUTATION_REQ;
}
- });
+ }));
}
private static void performLocally(Stage stage, Replica localReplica, final Runnable runnable, final RequestCallback> handler, Object description, Dispatcher.RequestTime requestTime)
{
- stage.maybeExecuteImmediately(new LocalMutationRunnable(localReplica, requestTime)
+ stage.maybeExecuteImmediately(Context.current().wrap(new LocalMutationRunnable(localReplica, requestTime)
{
public void runMayThrow()
{
+ // update span name if keyspace and table name are available
+ if (Span.current().getSpanContext().isValid())
+ {
+ String target;
+ if (description instanceof IMutation)
+ {
+ IMutation mutation = (IMutation) description;
+ target = mutation.getPartitionUpdates().stream()
+ .map((pu) -> pu.metadata().toString()).collect(Collectors.joining(" "));
+ }
+ else
+ {
+ target = description.toString();
+ }
+ Span.current().updateName(String.format("%s %s", verb().name(), target));
+ }
try
{
MessageParams.reset();
@@ -2038,6 +2087,7 @@ public void runMayThrow()
{
if (!(ex instanceof WriteTimeoutException) && !(ex instanceof RetryOnDifferentSystemException))
logger.error("Failed to apply mutation locally : ", ex);
+ Span.current().recordException(ex);
handler.onFailure(FBUtilities.getBroadcastAddressAndPort(), RequestFailure.forException(ex));
}
finally
@@ -2059,7 +2109,7 @@ protected Verb verb()
{
return Verb.MUTATION_REQ;
}
- });
+ }));
}
/**
@@ -2736,7 +2786,16 @@ public LocalReadRunnable(ReadCommand command, ReadCallback handler, Dispatcher.R
protected void runMayThrow()
{
- try
+ Span parentSpan = Span.current();
+ // Create additional Span for coordinator local read if the parent is from remote
+ // to prevent creating root span for internal read
+ Span localReadSpan = parentSpan.getSpanContext().isValid() ? Telemetry.getRequestTracer()
+ .spanBuilder(String.format("%s %s", verb.toString(), command.metadata().toString()))
+ .setAttribute(CassandraAttributes.THREAD_ID, Thread.currentThread().getId())
+ .setAttribute(CassandraAttributes.THREAD_NAME, Thread.currentThread().getName())
+ .startSpan()
+ : Span.getInvalid();
+ try (Scope scope = localReadSpan.makeCurrent())
{
MessageParams.reset();
@@ -2754,13 +2813,15 @@ protected void runMayThrow()
{
if (!command.isTrackingWarnings())
throw e;
-
+
+ localReadSpan.recordException(e);
response = command.createEmptyResponse();
readRejected = true;
}
catch (QueryCancelledException e)
{
logger.debug("Query cancelled (timeout)", e);
+ localReadSpan.recordException(e);
response = null;
Preconditions.checkState(!command.isCompleted(), "Local read marked as completed despite being aborted by timeout to table %s", command.metadata());
}
@@ -2782,6 +2843,8 @@ protected void runMayThrow()
}
catch (Throwable t)
{
+ localReadSpan.setStatus(StatusCode.ERROR, t.getMessage());
+ localReadSpan.recordException(t);
if (t instanceof TombstoneOverwhelmingException)
{
handler.onFailure(FBUtilities.getBroadcastAddressAndPort(), RequestFailure.READ_TOO_MANY_TOMBSTONES);
@@ -2793,6 +2856,10 @@ protected void runMayThrow()
throw t;
}
}
+ finally
+ {
+ localReadSpan.end();
+ }
}
@Override
@@ -3174,6 +3241,15 @@ private static abstract class LocalMutationRunnable implements RunnableDebuggabl
public final void run()
{
+ Span parentSpan = Span.current();
+ // Create additional Span for coordinator local mutation if the parent is valid
+ // to prevent creating root span for internal mutation
+ Span localMutationSpan = parentSpan.getSpanContext().isValid() ? Telemetry.getRequestTracer()
+ .spanBuilder(verb().name())
+ .setAttribute(CassandraAttributes.THREAD_ID, Thread.currentThread().getId())
+ .setAttribute(CassandraAttributes.THREAD_NAME, Thread.currentThread().getName())
+ .startSpan()
+ : Span.getInvalid();
final Verb verb = verb();
long now = MonotonicClock.Global.approxTime.now();
long deadline = requestTime.computeDeadline(verb.expiresAfterNanos());
@@ -3191,21 +3267,33 @@ public final void run()
{
protected void runMayThrow() throws Exception
{
- LocalMutationRunnable.this.runMayThrow();
+ try (Scope scope = localMutationSpan.makeCurrent())
+ {
+ LocalMutationRunnable.this.runMayThrow();
+ }
+ finally
+ {
+ localMutationSpan.end();
+ }
}
};
submitHint(runnable);
return;
}
- try
+ try (Scope scope = localMutationSpan.makeCurrent())
{
runMayThrow();
}
catch (Exception e)
{
+ localMutationSpan.recordException(e);
throw new RuntimeException(e);
}
+ finally
+ {
+ localMutationSpan.end();
+ }
}
@Override
diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java
index 2446dd7fa866..e967b3539ff6 100644
--- a/src/java/org/apache/cassandra/service/StorageService.java
+++ b/src/java/org/apache/cassandra/service/StorageService.java
@@ -218,6 +218,7 @@
import org.apache.cassandra.tcm.transformations.Register;
import org.apache.cassandra.tcm.transformations.Startup;
import org.apache.cassandra.tcm.transformations.Unregister;
+import org.apache.cassandra.telemetry.Telemetry;
import org.apache.cassandra.transport.ClientResourceLimits;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ExecutorUtils;
@@ -858,6 +859,13 @@ public void runMayThrow() throws InterruptedException, ExecutionException, IOExc
RegistrationStatus.instance.onRegistration();
Startup.maybeExecuteStartupTransformation(self);
+ Telemetry.init(DatabaseDescriptor.getClusterName(),
+ FBUtilities.getReleaseVersionString(),
+ FBUtilities.getBroadcastAddressAndPort().getHostAddress(false),
+ FBUtilities.getBroadcastAddressAndPort().getPort(),
+ Integer.toString(self.id()),
+ ClusterMetadata.current().locator.local());
+
if (CassandraRelevantProperties.SYNC_SYSTEM_PEERS_TABLES_AT_STARTUP.getBoolean())
SystemPeersValidator.validateAndRepair(ClusterMetadata.current());
diff --git a/src/java/org/apache/cassandra/service/reads/AbstractReadExecutor.java b/src/java/org/apache/cassandra/service/reads/AbstractReadExecutor.java
index bbe2a2d25e7f..b69a0e92a849 100644
--- a/src/java/org/apache/cassandra/service/reads/AbstractReadExecutor.java
+++ b/src/java/org/apache/cassandra/service/reads/AbstractReadExecutor.java
@@ -45,11 +45,12 @@
import org.apache.cassandra.service.StorageProxy.LocalReadRunnable;
import org.apache.cassandra.service.reads.repair.ReadRepair;
import org.apache.cassandra.tcm.ClusterMetadata;
-import org.apache.cassandra.tracing.TraceState;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.FBUtilities;
+import io.opentelemetry.context.Context;
+
import static com.google.common.collect.Iterables.all;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -73,7 +74,6 @@ public abstract class AbstractReadExecutor
protected final ReadRepair readRepair;
protected final DigestResolver digestResolver;
protected final ReadCallback handler;
- protected final TraceState traceState;
protected final ColumnFamilyStore cfs;
protected final Dispatcher.RequestTime requestTime;
@@ -91,7 +91,6 @@ public abstract class AbstractReadExecutor
this.digestResolver = new DigestResolver<>(coordinator, command, this.replicaPlan, requestTime);
this.handler = new ReadCallback<>(digestResolver, command, this.replicaPlan, requestTime);
this.cfs = cfs;
- this.traceState = Tracing.instance.get();
this.requestTime = requestTime;
@@ -152,8 +151,7 @@ private void makeRequests(ReadCommand readCommand, Iterable replicas)
continue;
}
- if (traceState != null)
- traceState.trace("reading {} from {}", readCommand.isDigestQuery() ? "digest" : "data", endpoint);
+ Tracing.trace("reading {} from {}", readCommand.isDigestQuery() ? "digest" : "data", endpoint);
if (null == message)
message = readCommand.createMessage(false, requestTime).withEpoch(ClusterMetadata.current().epoch);
@@ -165,7 +163,7 @@ private void makeRequests(ReadCommand readCommand, Iterable replicas)
if (hasLocalEndpoint)
{
logger.trace("reading {} locally", readCommand.isDigestQuery() ? "digest" : "data");
- Stage.READ.maybeExecuteImmediately(new LocalReadRunnable(readCommand, handler, requestTime));
+ Stage.READ.maybeExecuteImmediately(Context.current().wrap(new LocalReadRunnable(readCommand, handler, requestTime)));
}
}
@@ -358,8 +356,7 @@ public void maybeTryAdditionalReplicas()
// nor would we be able to speculate a new 'write' if the repair writes are insufficient
super.replicaPlan.addToContacts(extraReplica);
- if (traceState != null)
- traceState.trace("speculating read retry on {}", extraReplica);
+ Tracing.trace("speculating read retry on {}", extraReplica);
logger.trace("speculating read retry on {}", extraReplica);
MessagingService.instance().sendWithCallback(retryCommand.createMessage(false, requestTime), extraReplica.endpoint(), handler);
}
diff --git a/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java b/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java
index 6b2913320143..41a052ac08fa 100644
--- a/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java
+++ b/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java
@@ -78,6 +78,8 @@
import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.btree.BTreeSet;
+import io.opentelemetry.context.Context;
+
/**
* Helper in charge of collecting additional queries to be done on the coordinator to protect against invalid results
* being included due to replica-side filtering (secondary indexes or {@code ALLOW * FILTERING}).
@@ -172,7 +174,7 @@ private UnfilteredPartitionIterator executeReadCommand(ReadCommand cmd, Replica
if (source.isSelf() && coordinator.localReadSupported())
{
- Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(cmd, handler, requestTime));
+ Stage.READ.maybeExecuteImmediately(Context.current().wrap(new StorageProxy.LocalReadRunnable(cmd, handler, requestTime)));
}
else
{
diff --git a/src/java/org/apache/cassandra/service/reads/ShortReadPartitionsProtection.java b/src/java/org/apache/cassandra/service/reads/ShortReadPartitionsProtection.java
index d1562b1b4de9..d423ae898709 100644
--- a/src/java/org/apache/cassandra/service/reads/ShortReadPartitionsProtection.java
+++ b/src/java/org/apache/cassandra/service/reads/ShortReadPartitionsProtection.java
@@ -47,6 +47,8 @@
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.Dispatcher;
+import io.opentelemetry.context.Context;
+
public class ShortReadPartitionsProtection extends Transformation implements MorePartitions
{
private static final Logger logger = LoggerFactory.getLogger(ShortReadPartitionsProtection.class);
@@ -188,7 +190,7 @@ UnfilteredPartitionIterator executeReadCommand(ReadCommand cmd, ReplicaPlan.Shar
if (source.isSelf() && coordinator.localReadSupported())
{
- Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(cmd, handler, requestTime));
+ Stage.READ.maybeExecuteImmediately(Context.current().wrap(new StorageProxy.LocalReadRunnable(cmd, handler, requestTime)));
}
else
{
diff --git a/src/java/org/apache/cassandra/service/reads/range/RangeCommandIterator.java b/src/java/org/apache/cassandra/service/reads/range/RangeCommandIterator.java
index 62725a1d6d44..82737bba44f3 100644
--- a/src/java/org/apache/cassandra/service/reads/range/RangeCommandIterator.java
+++ b/src/java/org/apache/cassandra/service/reads/range/RangeCommandIterator.java
@@ -67,6 +67,8 @@
import org.apache.cassandra.utils.AbstractIterator;
import org.apache.cassandra.utils.CloseableIterator;
+import io.opentelemetry.context.Context;
+
import static com.google.common.base.Preconditions.checkState;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.readMetrics;
import static org.apache.cassandra.metrics.ClientRequestsMetricsHolder.readMetricsForLevel;
@@ -220,7 +222,7 @@ private SingleRangeResponse executeNormal(ReplicaPlan.ForRangeRead replicaPlan,
if (replicaPlan.contacts().size() == 1 && replicaPlan.contacts().get(0).isSelf() && readCoordinator.localReadSupported())
{
- Stage.READ.execute(new StorageProxy.LocalReadRunnable(rangeCommand, handler, requestTime, trackRepairedStatus));
+ Stage.READ.execute(Context.current().wrap(new StorageProxy.LocalReadRunnable(rangeCommand, handler, requestTime, trackRepairedStatus)));
}
else
{
diff --git a/src/java/org/apache/cassandra/service/reads/repair/AbstractReadRepair.java b/src/java/org/apache/cassandra/service/reads/repair/AbstractReadRepair.java
index 67c484297969..3e97dee2ae01 100644
--- a/src/java/org/apache/cassandra/service/reads/repair/AbstractReadRepair.java
+++ b/src/java/org/apache/cassandra/service/reads/repair/AbstractReadRepair.java
@@ -48,6 +48,8 @@
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.Dispatcher;
+import io.opentelemetry.context.Context;
+
import static java.util.concurrent.TimeUnit.MICROSECONDS;
public abstract class AbstractReadRepair, P extends ReplicaPlan.ForRead>
@@ -99,7 +101,7 @@ void sendReadCommand(Replica to, ReadCallback readCallback, boolean specul
if (to.isSelf() && coordinator.localReadSupported())
{
- Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(command, readCallback, requestTime, trackRepairedStatus));
+ Stage.READ.maybeExecuteImmediately(Context.current().wrap(new StorageProxy.LocalReadRunnable(command, readCallback, requestTime, trackRepairedStatus)));
return;
}
diff --git a/src/java/org/apache/cassandra/telemetry/CassandraAttributes.java b/src/java/org/apache/cassandra/telemetry/CassandraAttributes.java
new file mode 100644
index 000000000000..34ddae3341b3
--- /dev/null
+++ b/src/java/org/apache/cassandra/telemetry/CassandraAttributes.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.telemetry;
+
+import io.opentelemetry.api.common.AttributeKey;
+
+public final class CassandraAttributes
+{
+ // Until thread attributes are stabilized, we use these.
+ // See https://opentelemetry.io/docs/specs/semconv/registry/attributes/thread/
+ public static final AttributeKey THREAD_ID = AttributeKey.longKey("thread.id");
+ public static final AttributeKey THREAD_NAME = AttributeKey.stringKey("thread.name");
+
+ public static final AttributeKey CASSANDRA_DC = AttributeKey.stringKey("cassandra.dc");
+ public static final AttributeKey CASSANDRA_RACK = AttributeKey.stringKey("cassandra.rack");
+ public static final AttributeKey CASSANDRA_QUERY_TYPE = AttributeKey.stringKey("cassandra.query.type");
+ public static final AttributeKey CASSANDRA_PAGE_SIZE = AttributeKey.longKey("cassandra.page.size");
+ public static final AttributeKey CASSANDRA_CONSISTENCY_LEVEL = AttributeKey.stringKey("cassandra.consistency.level");
+ public static final AttributeKey CASSANDRA_SERIAL_CONSISTENCY_LEVEL = AttributeKey.stringKey("cassandra.serial.consistency.level");
+ public static final AttributeKey CASSANDRA_NET_VERB = AttributeKey.stringKey("cassandra.net.verb");
+ public static final AttributeKey CASSANDRA_COORDINATOR_ADDRESS = AttributeKey.stringKey("cassandra.coordinator.address");
+ public static final AttributeKey CASSANDRA_COORDINATOR_PORT = AttributeKey.longKey("cassandra.coordinator.port");
+
+ /** Value for {@code db.system.name} identifying Apache Cassandra. */
+ public static final String DB_SYSTEM_NAME_CASSANDRA = "cassandra";
+
+ private CassandraAttributes()
+ {
+ }
+}
diff --git a/src/java/org/apache/cassandra/telemetry/Telemetry.java b/src/java/org/apache/cassandra/telemetry/Telemetry.java
new file mode 100644
index 000000000000..d1d136995066
--- /dev/null
+++ b/src/java/org/apache/cassandra/telemetry/Telemetry.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.telemetry;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.tcm.membership.Location;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
+import io.opentelemetry.sdk.resources.Resource;
+import io.opentelemetry.semconv.ServerAttributes;
+import io.opentelemetry.semconv.ServiceAttributes;
+
+/**
+ * Holds references to OpenTelemetry objects
+ */
+public final class Telemetry
+{
+ private static volatile OpenTelemetry otel;
+
+ public static OpenTelemetry init(String clusterName,
+ String cassandraVersion,
+ String listenAddress,
+ int port,
+ String nodeId,
+ Location location)
+ {
+ if (otel != null) return otel;
+ synchronized (Telemetry.class)
+ {
+ if (otel != null) return otel;
+
+ if (DatabaseDescriptor.getOpenTelemetryEnabled())
+ {
+ Resource cassandraResource = Resource.builder()
+ .put(ServiceAttributes.SERVICE_NAME, clusterName)
+ .put(ServiceAttributes.SERVICE_NAMESPACE, "cassandra")
+ .put(ServiceAttributes.SERVICE_VERSION, cassandraVersion)
+ .put(ServiceAttributes.SERVICE_INSTANCE_ID, nodeId)
+ .put(ServerAttributes.SERVER_ADDRESS, listenAddress)
+ .put(ServerAttributes.SERVER_PORT, port)
+ .put(CassandraAttributes.CASSANDRA_DC, location.datacenter)
+ .put(CassandraAttributes.CASSANDRA_RACK, location.rack)
+ .build();
+ otel = AutoConfiguredOpenTelemetrySdk.builder()
+ .addPropertiesCustomizer((config) -> {
+ Map customConfig = new HashMap<>();
+ // Disable metrics and log export for now
+ customConfig.put("otel.metrics.exporter", "none");
+ customConfig.put("otel.logs.exporter", "none");
+ return customConfig;
+ })
+ .addResourceCustomizer((r, config) -> r.merge(cassandraResource))
+ .build()
+ .getOpenTelemetrySdk();
+ }
+ else
+ {
+ otel = OpenTelemetry.noop();
+ }
+ return otel;
+ }
+ }
+
+ private Telemetry()
+ {
+ }
+
+ @VisibleForTesting
+ static void setOpenTelemetryUnsafe(OpenTelemetry openTelemetry)
+ {
+ otel = openTelemetry;
+ }
+
+ /**
+ * Returns OpenTelemetry {@link Tracer} to trace client requests.
+ * Safe to call before `init` completes; returns noop tracer until initialization.
+ *
+ * @return Client request {@link Tracer}
+ */
+ public static Tracer getRequestTracer()
+ {
+ OpenTelemetry current = otel;
+ if (current == null)
+ current = OpenTelemetry.noop();
+ return current.getTracer("org.apache.cassandra.request");
+ }
+}
diff --git a/src/java/org/apache/cassandra/telemetry/tracing/CustomPayloadGetter.java b/src/java/org/apache/cassandra/telemetry/tracing/CustomPayloadGetter.java
new file mode 100644
index 000000000000..93459e24638e
--- /dev/null
+++ b/src/java/org/apache/cassandra/telemetry/tracing/CustomPayloadGetter.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.telemetry.tracing;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.util.Map;
+
+import org.apache.cassandra.utils.ByteBufferUtil;
+
+import io.opentelemetry.context.propagation.TextMapGetter;
+
+/**
+ * TextMapGetter that extracts W3C Trace Context from native protocol's custom payload
+ *
+ * In native protocol's custom payload, the following keys can be set to propagate tracing
+ * from the applications.
+ *
+ * - traceparent
+ * - tracestate
+ *
+ * OpenTelemetry only supports text format for propagating context right now.
+ * So the values associated with the above keys are Strings.
+ *
+ *
+ * @see W3C Trace Context
+ */
+public final class CustomPayloadGetter implements TextMapGetter