diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc
index 3425dd37b..5f575c7e0 100644
--- a/docs/src/main/asciidoc/_configprops.adoc
+++ b/docs/src/main/asciidoc/_configprops.adoc
@@ -10,6 +10,7 @@
|spring.sleuth.baggage.remote-fields | | List of fields that are referenced the same in-process as it is on the wire. For example, the field "x-vcap-request-id" would be set as-is including the prefix.
|spring.sleuth.baggage.tag-fields | | List of fields that should automatically become tags.
|spring.sleuth.batch.enabled | `true` | Enable Spring Batch instrumentation.
+|spring.sleuth.cassandra.enabled | `true` | Enable Cassandra instrumentation.
|spring.sleuth.circuitbreaker.enabled | `true` | Enable Spring Cloud CircuitBreaker instrumentation.
|spring.sleuth.config.server.enabled | `true` | Enable Spring Cloud Config Server instrumentation.
|spring.sleuth.deployer.enabled | `true` | Enable Spring Cloud Deployer instrumentation.
diff --git a/docs/src/main/asciidoc/_spans.adoc b/docs/src/main/asciidoc/_spans.adoc
index 2ded3abf3..bca12b758 100644
--- a/docs/src/main/asciidoc/_spans.adoc
+++ b/docs/src/main/asciidoc/_spans.adoc
@@ -85,6 +85,31 @@ Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.in
|batch.step.type|Type of the Spring Batch job.
|===
+=== Cassandra Span
+
+> Span created around CqlSession executions.
+
+**Span name** `%s` - since it contains `%s`, the name is dynamic and will be resolved at runtime.
+
+Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.cassandra.SleuthCassandraSpan`
+
+IMPORTANT: All tags and events must be prefixed with `cassandra.` prefix!
+
+.Tag Keys
+|===
+|Name | Description
+|cassandra.cql|A tag containing Cassandra CQL.
+|cassandra.keyspace|Name of the Cassandra keyspace.
+|cassandra.node[%s].error|A tag containing error that occurred for the given node. (since the name contains `%s` the final value will be resolved at runtime)
+|===
+
+.Event Values
+|===
+|Name | Description
+|cassandra.node.error|Set whenever an error occurred for the given node.
+|cassandra.node.success|Set when a success occurred for the session processing.
+|===
+
=== Circuit Breaker Function Span
> Span created when we wrap a Function passed to the CircuitBreaker. as fallback.
diff --git a/docs/src/main/asciidoc/integrations.adoc b/docs/src/main/asciidoc/integrations.adoc
index a0d24f277..9e1cb711d 100644
--- a/docs/src/main/asciidoc/integrations.adoc
+++ b/docs/src/main/asciidoc/integrations.adoc
@@ -665,6 +665,13 @@ This feature is available for all tracer implementations.
We're adding an instrumented Tomcat's `Valve` that originates the span.
In order to disable this instrumentation set `spring.sleuth.web.tomcat.enabled` to `false`.
+[[sleuth-cassandra-integration]]
+== Spring Data Cassandra
+
+This feature is available for all tracer implementations.
+
+We're instrumenting Casandra's `CqlSession` and `ReactiveSession` interfaces and we're providing our own implementation of the `RequestTracker`.
+In order to disable this instrumentation set `spring.sleuth.cassandra.enabled` to `false`.
[[sleuth-jdbc-integration]]
== Spring JDBC
diff --git a/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/docs/AssertingSpan.java b/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/docs/AssertingSpan.java
index d4ad3b7ff..0f9f47f23 100644
--- a/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/docs/AssertingSpan.java
+++ b/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/docs/AssertingSpan.java
@@ -132,6 +132,12 @@ public interface AssertingSpan extends Span {
return this;
}
+ @Override
+ default Span remoteIpAndPort(String ip, int port) {
+ getDelegate().remoteIpAndPort(ip, port);
+ return this;
+ }
+
/**
* @param documentedSpan span configuration
* @param span span to wrap in assertions
diff --git a/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/docs/AssertingSpanBuilder.java b/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/docs/AssertingSpanBuilder.java
index 0363928d2..de951bdc4 100644
--- a/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/docs/AssertingSpanBuilder.java
+++ b/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/docs/AssertingSpanBuilder.java
@@ -93,6 +93,12 @@ public interface AssertingSpanBuilder extends Span.Builder {
return this;
}
+ @Override
+ default Span.Builder remoteIpAndPort(String ip, int port) {
+ getDelegate().remoteIpAndPort(ip, port);
+ return this;
+ }
+
@Override
default AssertingSpanBuilder setParent(TraceContext context) {
getDelegate().setParent(context);
diff --git a/spring-cloud-sleuth-autoconfigure/pom.xml b/spring-cloud-sleuth-autoconfigure/pom.xml
index de8c4d1b1..cecaa8ace 100644
--- a/spring-cloud-sleuth-autoconfigure/pom.xml
+++ b/spring-cloud-sleuth-autoconfigure/pom.xml
@@ -123,6 +123,16 @@
spring-cloud-starter-task
true
+
+ org.springframework.boot
+ spring-boot-starter-data-cassandra
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-data-cassandra-reactive
+ true
+
org.springframework.cloud
spring-cloud-deployer-spi
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraAutoConfiguration.java
new file mode 100644
index 000000000..ca89e870a
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraAutoConfiguration.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.autoconfig.instrument.cassandra;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
+import org.springframework.cloud.sleuth.instrument.cassandra.TraceCqlSessionBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
+ * Auto-configuration} that registers instrumentation for Cassandra.
+ *
+ * @author Mark Paluch
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnBean(Tracer.class)
+@ConditionalOnProperty(value = "spring.sleuth.cassandra.enabled", matchIfMissing = true)
+@AutoConfigureAfter(BraveAutoConfiguration.class)
+@AutoConfigureBefore(CassandraAutoConfiguration.class)
+@ConditionalOnClass(CqlSession.class)
+public class TraceCassandraAutoConfiguration {
+
+ @Bean
+ static TraceCqlSessionBeanPostProcessor traceCqlSessionBeanPostProcessor(BeanFactory beanFactory) {
+ return new TraceCqlSessionBeanPostProcessor(beanFactory);
+ }
+
+ @Bean
+ TraceCqlSessionBuilderCustomizer traceCqlSessionBuilderCustomizer() {
+ return new TraceCqlSessionBuilderCustomizer();
+ }
+
+}
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraReactiveAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraReactiveAutoConfiguration.java
new file mode 100644
index 000000000..331fe53b4
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraReactiveAutoConfiguration.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.autoconfig.instrument.cassandra;
+
+import reactor.core.publisher.Flux;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.cassandra.ReactiveSession;
+
+/**
+ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
+ * Auto-configuration} that registers instrumentation for Cassandra.
+ *
+ * @author Mark Paluch
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnBean(Tracer.class)
+@ConditionalOnClass({ ReactiveSession.class, Flux.class })
+@ConditionalOnProperty(value = "spring.sleuth.cassandra.enabled", matchIfMissing = true)
+@AutoConfigureAfter(BraveAutoConfiguration.class)
+@AutoConfigureBefore({ CassandraAutoConfiguration.class, CassandraReactiveDataAutoConfiguration.class })
+public class TraceCassandraReactiveAutoConfiguration {
+
+ @Bean
+ static TraceReactiveSessionBeanPostProcessor traceReactiveSessionBeanPostProcessor(BeanFactory beanFactory) {
+ return new TraceReactiveSessionBeanPostProcessor(beanFactory);
+ }
+
+}
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCqlSessionBeanPostProcessor.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCqlSessionBeanPostProcessor.java
new file mode 100644
index 000000000..26cb74abc
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCqlSessionBeanPostProcessor.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.autoconfig.instrument.cassandra;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.cloud.sleuth.instrument.cassandra.TraceCqlSession;
+
+/**
+ * {@link BeanPostProcessor} to wrap a {@link CqlSession} instance into its trace
+ * representation.
+ *
+ * @author Marcin Grzejszczak
+ * @author Mark Paluch
+ * @since 3.1.0
+ */
+public class TraceCqlSessionBeanPostProcessor implements BeanPostProcessor {
+
+ private final BeanFactory beanFactory;
+
+ public TraceCqlSessionBeanPostProcessor(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName) {
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) {
+ if (bean instanceof CqlSession) {
+ return create((CqlSession) bean);
+ }
+ return bean;
+ }
+
+ private CqlSession create(CqlSession session) {
+ return TraceCqlSession.create(session, this.beanFactory);
+ }
+
+}
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceReactiveSessionBeanPostProcessor.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceReactiveSessionBeanPostProcessor.java
new file mode 100644
index 000000000..ddf6e74d0
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceReactiveSessionBeanPostProcessor.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.autoconfig.instrument.cassandra;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.cloud.sleuth.instrument.cassandra.TraceReactiveSession;
+import org.springframework.data.cassandra.ReactiveSession;
+
+/**
+ * {@link BeanPostProcessor} to wrap a
+ * {@link org.springframework.data.cassandra.ReactiveSession} instance into its trace
+ * representation.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+public class TraceReactiveSessionBeanPostProcessor implements BeanPostProcessor {
+
+ private final BeanFactory beanFactory;
+
+ public TraceReactiveSessionBeanPostProcessor(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName) {
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) {
+ if (bean instanceof ReactiveSession) {
+ return create((ReactiveSession) bean);
+ }
+ return bean;
+ }
+
+ private ReactiveSession create(ReactiveSession session) {
+ return TraceReactiveSession.create(session, this.beanFactory);
+ }
+
+}
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index 1cfec09f1..fac014bc6 100644
--- a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -191,6 +191,12 @@
"description": "Enable Spring Session instrumentation.",
"defaultValue": true
},
+ {
+ "name": "spring.sleuth.cassandra.enabled",
+ "type": "java.lang.Boolean",
+ "description": "Enable Cassandra instrumentation.",
+ "defaultValue": true
+ },
{
"name": "spring.sleuth.vault.enabled",
"type": "java.lang.Boolean",
diff --git a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories
index f859d669d..13decf094 100644
--- a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -6,6 +6,8 @@ org.springframework.cloud.sleuth.autoconfig.instrument.async.TraceAsyncAutoConfi
org.springframework.cloud.sleuth.autoconfig.instrument.async.TraceAsyncCustomAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.async.TraceAsyncDefaultAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.batch.TraceBatchAutoConfiguration,\
+org.springframework.cloud.sleuth.autoconfig.instrument.cassandra.TraceCassandraAutoConfiguration,\
+org.springframework.cloud.sleuth.autoconfig.instrument.cassandra.TraceCassandraReactiveAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.config.TraceSpringCloudConfigAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.circuitbreaker.TraceCircuitBreakerAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.deployer.TraceDeployerAutoConfiguration,\
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/rpc/BraveRpcAutoConfigurationIntegrationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/rpc/BraveRpcAutoConfigurationIntegrationTests.java
index 730c7a278..b245cfcaa 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/rpc/BraveRpcAutoConfigurationIntegrationTests.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/rpc/BraveRpcAutoConfigurationIntegrationTests.java
@@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
@@ -63,7 +64,7 @@ public class BraveRpcAutoConfigurationIntegrationTests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
- R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
+ R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class, CassandraAutoConfiguration.class })
@Configuration(proxyBeanMethods = false)
public static class Config {
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/web/client/WebClientTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/web/client/WebClientTests.java
index c5fcf7908..5d07ef4ad 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/web/client/WebClientTests.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/brave/instrument/web/client/WebClientTests.java
@@ -41,6 +41,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
@@ -152,7 +153,7 @@ public class WebClientTests {
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class,
- MongoAutoConfiguration.class, MongoDataAutoConfiguration.class })
+ CassandraAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class })
@DisableSecurity
public static class TestConfiguration {
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/async/TraceAsyncDefaultAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/async/TraceAsyncDefaultAutoConfigurationTests.java
index d96ce17c4..ca1dce937 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/async/TraceAsyncDefaultAutoConfigurationTests.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/async/TraceAsyncDefaultAutoConfigurationTests.java
@@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
@@ -54,7 +55,7 @@ public class TraceAsyncDefaultAutoConfigurationTests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
- R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
+ R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class, CassandraAutoConfiguration.class })
static class Config {
@Bean
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraAutoConfigurationTests.java
new file mode 100644
index 000000000..bcf141da1
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraAutoConfigurationTests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.autoconfig.instrument.cassandra;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+import org.assertj.core.api.BDDAssertions;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.sleuth.autoconfig.TraceNoOpAutoConfiguration;
+import org.springframework.cloud.sleuth.instrument.cassandra.TraceCqlSessionBuilderCustomizer;
+
+class TraceCassandraAutoConfigurationTests {
+
+ ApplicationContextRunner runner = new ApplicationContextRunner()
+ .withPropertyValues("spring.sleuth.noop.enabled=true").withConfiguration(
+ AutoConfigurations.of(TraceNoOpAutoConfiguration.class, TraceCassandraAutoConfiguration.class));
+
+ @Test
+ void should_register_cassandra_tracing_beans() {
+ runner.run(context -> BDDAssertions.then(context).hasSingleBean(TraceCqlSessionBeanPostProcessor.class)
+ .hasSingleBean(TraceCqlSessionBuilderCustomizer.class));
+ }
+
+ @Test
+ void should_not_register_cassandra_tracing_beans_when_cassandra_not_present() {
+ runner.withClassLoader(new FilteredClassLoader(CqlSession.class))
+ .run(context -> BDDAssertions.then(context).doesNotHaveBean(TraceCqlSessionBeanPostProcessor.class)
+ .doesNotHaveBean(TraceCqlSessionBuilderCustomizer.class));
+ }
+
+ @Test
+ void should_not_register_cassandra_tracing_beans_when_cassandra_tracing_disabled() {
+ runner.withPropertyValues("spring.sleuth.cassandra.enabled=false")
+ .run(context -> BDDAssertions.then(context).doesNotHaveBean(TraceCqlSessionBeanPostProcessor.class)
+ .doesNotHaveBean(TraceCqlSessionBuilderCustomizer.class));
+ }
+
+}
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraReactiveAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraReactiveAutoConfigurationTests.java
new file mode 100644
index 000000000..68b77b955
--- /dev/null
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/cassandra/TraceCassandraReactiveAutoConfigurationTests.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.autoconfig.instrument.cassandra;
+
+import org.assertj.core.api.BDDAssertions;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.cloud.sleuth.autoconfig.TraceNoOpAutoConfiguration;
+import org.springframework.data.cassandra.ReactiveSession;
+
+class TraceCassandraReactiveAutoConfigurationTests {
+
+ ApplicationContextRunner runner = new ApplicationContextRunner()
+ .withPropertyValues("spring.sleuth.noop.enabled=true").withConfiguration(AutoConfigurations
+ .of(TraceNoOpAutoConfiguration.class, TraceCassandraReactiveAutoConfiguration.class));
+
+ @Test
+ void should_register_cassandra_tracing_beans() {
+ runner.run(context -> BDDAssertions.then(context).hasSingleBean(TraceReactiveSessionBeanPostProcessor.class));
+ }
+
+ @Test
+ void should_not_register_cassandra_tracing_beans_when_cassandra_not_present() {
+ runner.withClassLoader(new FilteredClassLoader(ReactiveSession.class)).run(
+ context -> BDDAssertions.then(context).doesNotHaveBean(TraceReactiveSessionBeanPostProcessor.class));
+ }
+
+ @Test
+ void should_not_register_cassandra_tracing_beans_when_cassandra_tracing_disabled() {
+ runner.withPropertyValues("spring.sleuth.cassandra.enabled=false").run(
+ context -> BDDAssertions.then(context).doesNotHaveBean(TraceReactiveSessionBeanPostProcessor.class));
+ }
+
+}
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/quartz/TraceQuartzAutoConfigurationTest.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/quartz/TraceQuartzAutoConfigurationTest.java
index 8ce3abc66..7265b4e5c 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/quartz/TraceQuartzAutoConfigurationTest.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/quartz/TraceQuartzAutoConfigurationTest.java
@@ -26,6 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.security.servlet.Managemen
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
@@ -120,7 +121,7 @@ public class TraceQuartzAutoConfigurationTest {
exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
- R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class },
+ R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class, CassandraAutoConfiguration.class },
excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration")
public static class EnableAutoConfig {
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/BraveWebClientAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/BraveWebClientAutoConfigurationTests.java
index 016442269..2e0c9f37a 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/BraveWebClientAutoConfigurationTests.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/BraveWebClientAutoConfigurationTests.java
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
@@ -124,7 +125,7 @@ public class BraveWebClientAutoConfigurationTests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
- R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
+ R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class, CassandraAutoConfiguration.class })
static class Config {
// custom builder
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/GH846Tests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/GH846Tests.java
index 0801a617d..900b3c447 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/GH846Tests.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/GH846Tests.java
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
@@ -58,7 +59,7 @@ public class GH846Tests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
- R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
+ R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class, CassandraAutoConfiguration.class })
static class App {
@Bean
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientDisabledTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientDisabledTests.java
index 07c6b8e7b..35c3d536a 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientDisabledTests.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/web/client/TraceWebClientDisabledTests.java
@@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
@@ -47,7 +48,7 @@ public class TraceWebClientDisabledTests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
- R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
+ R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class, CassandraAutoConfiguration.class })
public static class Config {
}
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSamplerTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSamplerTests.java
index b3061be84..34103f05a 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSamplerTests.java
+++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/zipkin2/ZipkinSamplerTests.java
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
@@ -58,7 +59,7 @@ public class ZipkinSamplerTests {
exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
- R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class },
+ R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class, CassandraAutoConfiguration.class },
excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration")
static class TestConfig {
diff --git a/spring-cloud-sleuth-autoconfigure/src/test/resources/application.yml b/spring-cloud-sleuth-autoconfigure/src/test/resources/application.yml
index e5b38daa4..151a645e7 100644
--- a/spring-cloud-sleuth-autoconfigure/src/test/resources/application.yml
+++ b/spring-cloud-sleuth-autoconfigure/src/test/resources/application.yml
@@ -1,3 +1,3 @@
logging.level.org.springframework.cloud: DEBUG
-spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, org.springframework.cloud.gateway.config.GatewayAutoConfiguration, org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration, org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration, org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration, org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration, org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration, org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration, org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
+spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, org.springframework.cloud.gateway.config.GatewayAutoConfiguration, org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration, org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration, org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration, org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration, org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration, org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration, org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration, org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
diff --git a/spring-cloud-sleuth-instrumentation/pom.xml b/spring-cloud-sleuth-instrumentation/pom.xml
index fd7a5aa4d..4af8bedbc 100644
--- a/spring-cloud-sleuth-instrumentation/pom.xml
+++ b/spring-cloud-sleuth-instrumentation/pom.xml
@@ -167,6 +167,16 @@
spring-cloud-starter-task
true
+
+ org.springframework.boot
+ spring-boot-starter-data-cassandra
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-data-cassandra-reactive
+ true
+
org.springframework.cloud
spring-cloud-deployer-spi
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/CassandraSpanCustomizer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/CassandraSpanCustomizer.java
new file mode 100644
index 000000000..6d7b98d14
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/CassandraSpanCustomizer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2018-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+/**
+ * Provides additional customization for a Cassandra span. Used internally - do not
+ * implement.
+ *
+ * @author Mark Paluch
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+public interface CassandraSpanCustomizer {
+
+ /**
+ * Provides additional customization for a Cassandra span.
+ * @param defaultName default name of the span
+ */
+ void customizeSpan(String defaultName);
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/CassandraSpanSupplier.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/CassandraSpanSupplier.java
new file mode 100644
index 000000000..ab5115a78
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/CassandraSpanSupplier.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import org.springframework.cloud.sleuth.Span;
+
+/**
+ * Returns the Cassandra span.
+ *
+ * Used internally - do not implement.
+ *
+ * @author Mark Paluch
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+public interface CassandraSpanSupplier {
+
+ /**
+ * @return span
+ */
+ Span getSpan();
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/SleuthCassandraSpan.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/SleuthCassandraSpan.java
new file mode 100644
index 000000000..d0752df51
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/SleuthCassandraSpan.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import org.springframework.cloud.sleuth.docs.DocumentedSpan;
+import org.springframework.cloud.sleuth.docs.EventValue;
+import org.springframework.cloud.sleuth.docs.TagKey;
+
+enum SleuthCassandraSpan implements DocumentedSpan {
+
+ /**
+ * Span created around CqlSession executions.
+ */
+ CASSANDRA_SPAN {
+ @Override
+ public String getName() {
+ return "%s";
+ }
+
+ @Override
+ public TagKey[] getTagKeys() {
+ return Tags.values();
+ }
+
+ @Override
+ public EventValue[] getEvents() {
+ return Events.values();
+ }
+
+ @Override
+ public String prefix() {
+ return "cassandra.";
+ }
+ };
+
+ enum Tags implements TagKey {
+
+ /**
+ * Name of the Cassandra keyspace.
+ */
+ KEYSPACE_NAME {
+ @Override
+ public String getKey() {
+ return "cassandra.keyspace";
+ }
+ },
+
+ /**
+ * A tag containing error that occurred for the given node.
+ */
+ NODE_ERROR_TAG {
+ @Override
+ public String getKey() {
+ return "cassandra.node[%s].error";
+ }
+ },
+
+ /**
+ * A tag containing Cassandra CQL.
+ */
+ CQL_TAG {
+ @Override
+ public String getKey() {
+ return "cassandra.cql";
+ }
+ },
+
+ }
+
+ enum Events implements EventValue {
+
+ /**
+ * Set whenever an error occurred for the given node.
+ */
+ NODE_ERROR {
+ @Override
+ public String getValue() {
+ return "cassandra.node.error";
+ }
+ },
+
+ /**
+ * Set when a success occurred for the session processing.
+ */
+ NODE_SUCCESS {
+ @Override
+ public String getValue() {
+ return "cassandra.node.success";
+ }
+ }
+
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSession.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSession.java
new file mode 100644
index 000000000..0c1f0f48a
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSession.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.beans.factory.BeanFactory;
+
+/**
+ * Factory to create a {@link TraceCqlSession}.
+ *
+ * @author Mark Paluch
+ * @since 3.1.0
+ */
+public final class TraceCqlSession {
+
+ private TraceCqlSession() {
+ throw new IllegalStateException("Can't instantiate a utility class");
+ }
+
+ public static CqlSession create(CqlSession session, BeanFactory beanFactory) {
+ ProxyFactory proxyFactory = new ProxyFactory();
+ proxyFactory.setTarget(session);
+ proxyFactory.addAdvice(new TraceCqlSessionInterceptor(session, beanFactory));
+ proxyFactory.addInterface(CqlSession.class);
+ return (CqlSession) proxyFactory.getProxy();
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionBuilderCustomizer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionBuilderCustomizer.java
new file mode 100644
index 000000000..2420d27c8
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionBuilderCustomizer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import com.datastax.oss.driver.api.core.CqlSessionBuilder;
+
+import org.springframework.boot.autoconfigure.cassandra.CqlSessionBuilderCustomizer;
+
+/**
+ * Adds tracing mechanism to the {@link CqlSessionBuilder}.
+ *
+ * @author Mark Paluch
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+public class TraceCqlSessionBuilderCustomizer implements CqlSessionBuilderCustomizer {
+
+ @Override
+ public void customize(CqlSessionBuilder cqlSessionBuilder) {
+ cqlSessionBuilder.withRequestTracker(TraceRequestTracker.INSTANCE);
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionInterceptor.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionInterceptor.java
new file mode 100644
index 000000000..d06972e37
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionInterceptor.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2018-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+import com.datastax.oss.driver.api.core.CqlIdentifier;
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.cql.SimpleStatement;
+import com.datastax.oss.driver.api.core.cql.Statement;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.cloud.sleuth.CurrentTraceContext;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.docs.AssertingSpanBuilder;
+import org.springframework.cloud.sleuth.internal.ContextUtil;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+/**
+ * A {@link MethodInterceptor} that wraps calls around {@link CqlSession} in a trace
+ * representation. This interceptor wraps statements for {@code execute} and
+ * {@code prepare} (including their asynchronous variants) only. Graph and reactive
+ * {@link CqlSession} method remain called as-is.
+ *
+ * @author Mark Paluch
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+class TraceCqlSessionInterceptor implements MethodInterceptor {
+
+ private static final Log log = LogFactory.getLog(TraceCqlSessionInterceptor.class);
+
+ private final CqlSession delegate;
+
+ private final BeanFactory beanFactory;
+
+ private Tracer tracer;
+
+ private CurrentTraceContext currentTraceContext;
+
+ TraceCqlSessionInterceptor(CqlSession delegate, BeanFactory beanFactory) {
+ this.delegate = delegate;
+ this.beanFactory = beanFactory;
+ }
+
+ @Nullable
+ @Override
+ public Object invoke(@NonNull MethodInvocation invocation) throws Throwable {
+ Method method = invocation.getMethod();
+ Object[] args = invocation.getArguments();
+ if (isContextUnusable()) {
+ return invocation.proceed();
+ }
+ if (method.getName().equals("execute")) {
+ if (args.length > 0) {
+ return tracedCall(createStatement(args), "execute", this.delegate::execute);
+ }
+ }
+ if (method.getName().equals("executeAsync")) {
+ if (args.length > 0) {
+ return tracedCall(createStatement(args), "executeAsync", this.delegate::executeAsync);
+ }
+ }
+ if (method.getName().equals("prepare")) {
+ if (args.length > 0) {
+ return tracedCall(createStatement(args), "prepare",
+ statement -> this.delegate.prepare((SimpleStatement) statement));
+ }
+ }
+ if (method.getName().equals("prepareAsync")) {
+ if (args.length > 0) {
+ return tracedCall(createStatement(args), "prepareAsync",
+ statement -> this.delegate.prepareAsync((SimpleStatement) statement));
+ }
+ }
+ return invocation.proceed();
+ }
+
+ private static Statement> createStatement(Object[] args) {
+ if (args[0] instanceof Statement) {
+ return (Statement>) (args[0]);
+ }
+ else if (args[0] instanceof String && args.length == 1) {
+ return SimpleStatement.newInstance((String) args[0]);
+ }
+ else if (args[0] instanceof String && args.length == 2) {
+ String query = (String) args[0];
+ return args[1] instanceof Map ? SimpleStatement.newInstance(query, (Map) args[1])
+ : SimpleStatement.newInstance(query, (Object[]) args[1]);
+ }
+ throw new IllegalArgumentException(String.format("Unsupported arguments %s", Arrays.toString(args)));
+ }
+
+ boolean isContextUnusable() {
+ return ContextUtil.isContextUnusable(this.beanFactory);
+ }
+
+ private Object tracedCall(Statement> statement, String defaultSpanName, Function, Object> function) {
+ Span span = cassandraClientSpan();
+ Statement> proxied = TraceStatement.isTraceStatement(statement) ? statement
+ : TraceStatement.createProxy(span, statement);
+ ((CassandraSpanCustomizer) proxied).customizeSpan(defaultSpanName);
+ try (CurrentTraceContext.Scope ws = currentTraceContext().maybeScope(span.context())) {
+ log.debug("Will execute statement");
+ return function.apply(proxied);
+ }
+ }
+
+ private Span cassandraClientSpan() {
+ return cassandraClientSpan(tracer().spanBuilder(), getSessionName(), getKeyspace());
+ }
+
+ static Span cassandraClientSpan(Span.Builder builder, String sessionName, Optional keyspace) {
+ return AssertingSpanBuilder.of(SleuthCassandraSpan.CASSANDRA_SPAN, builder).kind(Span.Kind.CLIENT)
+ .remoteServiceName("cassandra-" + sessionName)
+ .tag(SleuthCassandraSpan.Tags.KEYSPACE_NAME, keyspace.map(CqlIdentifier::asInternal).orElse("unknown"))
+ .start();
+ }
+
+ String getSessionName() {
+ return this.delegate.getContext().getSessionName();
+ }
+
+ Optional getKeyspace() {
+ return this.delegate.getKeyspace();
+ }
+
+ private Tracer tracer() {
+ if (this.tracer == null) {
+ this.tracer = this.beanFactory.getBean(Tracer.class);
+ }
+ return this.tracer;
+ }
+
+ private CurrentTraceContext currentTraceContext() {
+ if (this.currentTraceContext == null) {
+ this.currentTraceContext = this.beanFactory.getBean(CurrentTraceContext.class);
+ }
+ return this.currentTraceContext;
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceReactiveSession.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceReactiveSession.java
new file mode 100644
index 000000000..dd94e0ec3
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceReactiveSession.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2018-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import java.util.Map;
+import java.util.Optional;
+
+import com.datastax.oss.driver.api.core.context.DriverContext;
+import com.datastax.oss.driver.api.core.cql.PreparedStatement;
+import com.datastax.oss.driver.api.core.cql.SimpleStatement;
+import com.datastax.oss.driver.api.core.cql.Statement;
+import reactor.core.publisher.Mono;
+import reactor.util.context.ContextView;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.cloud.sleuth.CurrentTraceContext;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.TraceContext;
+import org.springframework.cloud.sleuth.Tracer;
+import org.springframework.cloud.sleuth.instrument.reactor.ReactorSleuth;
+import org.springframework.data.cassandra.ReactiveResultSet;
+import org.springframework.data.cassandra.ReactiveSession;
+
+/**
+ * Tracing variant of {@link ReactiveSession}.
+ *
+ * @author Mark Paluch
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+public class TraceReactiveSession implements ReactiveSession {
+
+ private final ReactiveSession delegate;
+
+ private final BeanFactory beanFactory;
+
+ private Tracer tracer;
+
+ private CurrentTraceContext currentTraceContext;
+
+ TraceReactiveSession(ReactiveSession delegate, BeanFactory beanFactory) {
+ this.delegate = delegate;
+ this.beanFactory = beanFactory;
+ }
+
+ /**
+ * Factory method for creation of a {@link TraceReactiveSession}.
+ * @param session reactive session
+ * @param beanFactory bean factory
+ * @return traced representation of a {@link ReactiveSession}.
+ */
+ public static ReactiveSession create(ReactiveSession session, BeanFactory beanFactory) {
+ return new TraceReactiveSession(session, beanFactory);
+ }
+
+ @Override
+ public boolean isClosed() {
+ return this.delegate.isClosed();
+ }
+
+ @Override
+ public DriverContext getContext() {
+ return this.delegate.getContext();
+ }
+
+ @Override
+ public Mono execute(String cql) {
+ return execute(SimpleStatement.newInstance(cql));
+ }
+
+ @Override
+ public Mono execute(String cql, Object... objects) {
+ return execute(SimpleStatement.newInstance(cql, objects));
+ }
+
+ @Override
+ public Mono execute(String cql, Map map) {
+ return execute(SimpleStatement.newInstance(cql, map));
+ }
+
+ @Override
+ public Mono execute(Statement> statement) {
+ return Mono.deferContextual(contextView -> {
+ Span span = ReactorSleuth.spanFromContext(tracer(), currentTraceContext(), contextView);
+ return this.delegate.execute(proxiedStatement(span, statement, "execute"));
+ }).contextWrite(context -> ReactorSleuth.putSpanInScope(tracer(), context, createSpan(context)));
+ }
+
+ @Override
+ public Mono prepare(String cql) {
+ return prepare(SimpleStatement.newInstance(cql));
+ }
+
+ @Override
+ public Mono prepare(SimpleStatement statement) {
+ return Mono.deferContextual(contextView -> {
+ Span span = ReactorSleuth.spanFromContext(tracer(), currentTraceContext(), contextView);
+ return this.delegate.prepare((SimpleStatement) proxiedStatement(span, statement, "prepare"));
+ }).contextWrite(context -> ReactorSleuth.putSpanInScope(tracer(), context, createSpan(context)));
+ }
+
+ private Statement> proxiedStatement(Span span, Statement> statement, String defaultName) {
+ Statement> proxied = TraceStatement.createProxy(span, statement);
+ ((CassandraSpanCustomizer) proxied).customizeSpan(defaultName);
+ return proxied;
+ }
+
+ @Override
+ public void close() {
+ this.delegate.close();
+ }
+
+ private Span createSpan(ContextView contextView) {
+ return TraceCqlSessionInterceptor.cassandraClientSpan(spanBuilder(contextView), getContext().getSessionName(),
+ Optional.empty() /* todo @since 3.2.2 */);
+ }
+
+ private Span.Builder spanBuilder(ContextView contextView) {
+ Span.Builder spanBuilder = tracer().spanBuilder();
+ if (contextView.hasKey(TraceContext.class)) {
+ return spanBuilder.setParent(contextView.get(TraceContext.class));
+ }
+ else if (contextView.hasKey(Span.class)) {
+ return spanBuilder.setParent(contextView.get(Span.class).context());
+ }
+ Span span = tracer().currentSpan();
+ if (span != null) {
+ return spanBuilder.setParent(span.context());
+ }
+ return spanBuilder;
+ }
+
+ private CurrentTraceContext currentTraceContext() {
+ if (this.currentTraceContext == null) {
+ this.currentTraceContext = beanFactory.getBean(CurrentTraceContext.class);
+ }
+ return this.currentTraceContext;
+ }
+
+ private Tracer tracer() {
+ if (this.tracer == null) {
+ this.tracer = beanFactory.getBean(Tracer.class);
+ }
+ return this.tracer;
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceRequestTracker.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceRequestTracker.java
new file mode 100644
index 000000000..5b32056ce
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceRequestTracker.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2018-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
+import com.datastax.oss.driver.api.core.metadata.Node;
+import com.datastax.oss.driver.api.core.session.Request;
+import com.datastax.oss.driver.api.core.session.Session;
+import com.datastax.oss.driver.api.core.tracker.RequestTracker;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.docs.AssertingSpan;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+import static org.springframework.cloud.sleuth.instrument.cassandra.SleuthCassandraSpan.CASSANDRA_SPAN;
+import static org.springframework.cloud.sleuth.instrument.cassandra.SleuthCassandraSpan.Events.NODE_ERROR;
+import static org.springframework.cloud.sleuth.instrument.cassandra.SleuthCassandraSpan.Events.NODE_SUCCESS;
+import static org.springframework.cloud.sleuth.instrument.cassandra.SleuthCassandraSpan.Tags.NODE_ERROR_TAG;
+
+/**
+ * Trace implementation of the {@link RequestTracker}.
+ *
+ * @author Mark Paluch
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+enum TraceRequestTracker implements RequestTracker {
+
+ INSTANCE;
+
+ private static final Log log = LogFactory.getLog(TraceRequestTracker.class);
+
+ @Override
+ public void onSuccess(@NonNull Request request, long latencyNanos, @NonNull DriverExecutionProfile executionProfile,
+ @NonNull Node node, @NonNull String requestLogPrefix) {
+ if (request instanceof CassandraSpanSupplier) {
+ Span span = ((CassandraSpanSupplier) request).getSpan();
+ if (log.isDebugEnabled()) {
+ log.debug("Closing span [" + span + "]");
+ }
+ span.end();
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Request request, @NonNull Throwable error, long latencyNanos,
+ @NonNull DriverExecutionProfile executionProfile, @Nullable Node node, @NonNull String requestLogPrefix) {
+ if (request instanceof CassandraSpanSupplier) {
+ Span span = ((CassandraSpanSupplier) request).getSpan();
+ span.error(error);
+ if (log.isDebugEnabled()) {
+ log.debug("Closing span [" + span + "]");
+ }
+ span.end();
+ }
+ }
+
+ @Override
+ public void onNodeError(@NonNull Request request, @NonNull Throwable error, long latencyNanos,
+ @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, @NonNull String requestLogPrefix) {
+ if (request instanceof CassandraSpanSupplier) {
+ AssertingSpan span = AssertingSpan.of(CASSANDRA_SPAN, ((CassandraSpanSupplier) request).getSpan());
+ span.event(NODE_ERROR);
+ span.tag(String.format(NODE_ERROR_TAG.getKey(), node.getEndPoint()), error.toString());
+ tryAddingRemoteIpAndPort(node, span);
+ if (log.isDebugEnabled()) {
+ log.debug("Marking node error for [" + span + "]");
+ }
+ }
+ }
+
+ @Override
+ public void onNodeSuccess(@NonNull Request request, long latencyNanos,
+ @NonNull DriverExecutionProfile executionProfile, @NonNull Node node, @NonNull String requestLogPrefix) {
+ if (request instanceof CassandraSpanSupplier) {
+ AssertingSpan span = AssertingSpan.of(CASSANDRA_SPAN, ((CassandraSpanSupplier) request).getSpan());
+ span.event(NODE_SUCCESS);
+ tryAddingRemoteIpAndPort(node, span);
+ if (log.isDebugEnabled()) {
+ log.debug("Marking node success for [" + span + "]");
+ }
+ }
+ }
+
+ @Override
+ public void onSessionReady(@NonNull Session session) {
+ }
+
+ @Override
+ public void close() throws Exception {
+
+ }
+
+ private void tryAddingRemoteIpAndPort(Node node, Span span) {
+ try {
+ SocketAddress socketAddress = node.getEndPoint().resolve();
+ String host;
+ int port;
+ if (socketAddress instanceof InetSocketAddress) {
+ InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+ host = inetSocketAddress.getHostString();
+ port = inetSocketAddress.getPort();
+ }
+ else {
+ host = socketAddress.toString();
+ port = 0;
+ }
+ span.remoteIpAndPort(host, port);
+ }
+ catch (Exception e) {
+ log.debug("Exception occurred while trying to set ip and port", e);
+ }
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceStatement.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceStatement.java
new file mode 100644
index 000000000..91a036878
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceStatement.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2018-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import java.util.StringJoiner;
+
+import javax.annotation.Nonnull;
+
+import com.datastax.oss.driver.api.core.cql.BatchStatement;
+import com.datastax.oss.driver.api.core.cql.BatchableStatement;
+import com.datastax.oss.driver.api.core.cql.BoundStatement;
+import com.datastax.oss.driver.api.core.cql.SimpleStatement;
+import com.datastax.oss.driver.api.core.cql.Statement;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.docs.AssertingSpan;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Trace implementation of a {@link Statement}.
+ *
+ * @author Mark Paluch
+ * @author Marcin Grzejszczak
+ * @since 3.1.0
+ */
+final class TraceStatement implements MethodInterceptor {
+
+ private final Span span;
+
+ private Statement> delegate;
+
+ private TraceStatement(Span span, Statement> delegate) {
+ this.span = span;
+ this.delegate = delegate;
+ }
+
+ /**
+ * Creates a proxy with {@link TraceStatement} attached to it.
+ * @param span current span
+ * @param target target to proxy
+ * @param type of target
+ * @return proxied object with trace advice
+ */
+ @SuppressWarnings("unchecked")
+ public static T createProxy(Span span, T target) {
+ ProxyFactory factory = new ProxyFactory(ClassUtils.getAllInterfaces(target));
+ factory.addInterface(CassandraSpanCustomizer.class);
+ factory.addInterface(CassandraSpanSupplier.class);
+ factory.setTarget(target);
+ factory.addAdvice(new TraceStatement(span, (Statement>) target));
+ return (T) factory.getProxy();
+ }
+
+ /**
+ * @param statement target statement to inspect
+ * @return whether the given {@code statement} is a traced statement wrapper
+ */
+ public static boolean isTraceStatement(Statement> statement) {
+ return statement instanceof CassandraSpanCustomizer && statement instanceof CassandraSpanSupplier;
+ }
+
+ /**
+ * @return stored span
+ */
+ public Span getSpan() {
+ return this.span;
+ }
+
+ /**
+ * Tries to parse the CQL query or provides the default name.
+ * @param defaultName if there's not query
+ * @return span name
+ */
+ public String getSpanName(String defaultName) {
+ String query = getCql();
+ if (query.indexOf(' ') > -1) {
+ return query.substring(0, query.indexOf(' '));
+ }
+ return defaultName;
+ }
+
+ private String getCql() {
+ String query = "";
+ if (this.delegate instanceof SimpleStatement) {
+ query = getQuery(this.delegate);
+ }
+ else if (this.delegate instanceof BoundStatement) {
+ query = getQuery(this.delegate);
+ }
+ else if (this.delegate instanceof BatchStatement) {
+ StringJoiner joiner = new StringJoiner(";");
+ for (BatchableStatement> bs : (BatchStatement) this.delegate) {
+ joiner.add(getQuery(bs));
+ }
+ query = joiner.toString();
+ }
+ return query;
+ }
+
+ private static String getQuery(Statement> statement) {
+ if (statement instanceof SimpleStatement) {
+ return ((SimpleStatement) statement).getQuery();
+ }
+ else if (statement instanceof BoundStatement) {
+ return ((BoundStatement) statement).getPreparedStatement().getQuery();
+ }
+ return "";
+ }
+
+ @Nullable
+ @Override
+ public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
+ if (invocation.getMethod().getName().equals("getSpan")) {
+ return this.span;
+ }
+ if (invocation.getMethod().getName().equals("customizeSpan")) {
+ AssertingSpan.of(SleuthCassandraSpan.CASSANDRA_SPAN, this.span)
+ .name(getSpanName((String) invocation.getArguments()[0]))
+ .tag(SleuthCassandraSpan.Tags.CQL_TAG, getCql());
+ return null;
+ }
+ Object result = invocation.proceed();
+ if (result instanceof Statement>) {
+ this.delegate = (Statement>) result;
+ }
+ return result;
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/TraceListenerStrategySpanCustomizer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/TraceListenerStrategySpanCustomizer.java
index 52fe0ae6b..4e592d673 100644
--- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/TraceListenerStrategySpanCustomizer.java
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/jdbc/TraceListenerStrategySpanCustomizer.java
@@ -30,6 +30,7 @@ public interface TraceListenerStrategySpanCustomizer
/**
* Customizes the client database span.
+ * @param dataSource data source for which we're building the span
* @param spanBuilder span builder
*/
void customizeConnectionSpan(T dataSource, Span.Builder spanBuilder);
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java
index 8b5e99e3f..3ed8f2faf 100644
--- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java
@@ -35,6 +35,7 @@ import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Operators;
import reactor.util.context.Context;
+import reactor.util.context.ContextView;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.Span;
@@ -544,6 +545,7 @@ public abstract class ReactorSleuth {
* Updates the Reactor context with tracing information. Creates a new span if there
* is no current span. Creates a child span if there was an entry in the context
* already.
+ * @param tracer tracer
* @param currentTraceContext current trace context
* @param context Reactor context
* @param childSpanName child span name when there is no span in context
@@ -572,11 +574,49 @@ public abstract class ReactorSleuth {
return putSpanInScope(tracer, context, span);
}
- private static Context putSpanInScope(Tracer tracer, Context context, Span span) {
+ /**
+ * Puts the provided span in scope and in Reactor context.
+ * @param tracer tracer
+ * @param context Reactor context
+ * @param span span to put in Reactor context
+ * @return mutated context
+ */
+ public static Context putSpanInScope(Tracer tracer, Context context, Span span) {
return context.put(Span.class, span).put(TraceContext.class, span.context()).put(Tracer.SpanInScope.class,
tracer.withSpan(span));
}
+ /**
+ * Retrieves span from Reactor context.
+ * @param tracer tracer
+ * @param currentTraceContext current trace context
+ * @param context context view
+ * @return span from Reactor context or creates a new one if missing
+ */
+ public static Span spanFromContext(Tracer tracer, CurrentTraceContext currentTraceContext, ContextView context) {
+ Span span = context.getOrDefault(Span.class, null);
+ if (span != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("Found a span in reactor context [" + span + "]");
+ }
+ return span;
+ }
+ TraceContext traceContext = context.getOrDefault(TraceContext.class, null);
+ if (traceContext != null) {
+ try (CurrentTraceContext.Scope scope = currentTraceContext.maybeScope(traceContext)) {
+ if (log.isDebugEnabled()) {
+ log.debug("Found a trace context in reactor context [" + traceContext + "]");
+ }
+ return tracer.currentSpan();
+ }
+ }
+ Span newSpan = tracer.nextSpan().start();
+ if (log.isDebugEnabled()) {
+ log.debug("No span was found - will create a new one [" + newSpan + "]");
+ }
+ return newSpan;
+ }
+
}
class SleuthContextOperator implements Subscription, CoreSubscriber, Scannable {
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletRequestWrapper.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletRequestWrapper.java
index 8fe1bc235..a692c6d5f 100644
--- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletRequestWrapper.java
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletRequestWrapper.java
@@ -33,7 +33,11 @@ import org.springframework.lang.Nullable;
*/
public class HttpServletRequestWrapper implements HttpServerRequest {
- /** @since 5.10 */
+ /**
+ * Wraps the request in a tracing representation.
+ * @param request http request
+ * @return wrapped request
+ */
public static HttpServerRequest create(HttpServletRequest request) {
return new HttpServletRequestWrapper(request);
}
diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletResponseWrapper.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletResponseWrapper.java
index 3d70cbd80..de4e30b86 100644
--- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletResponseWrapper.java
+++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/servlet/HttpServletResponseWrapper.java
@@ -36,8 +36,9 @@ public class HttpServletResponseWrapper implements HttpServerResponse {
// not final for inner
// subtype
/**
+ * Returns the trace representation of a response.
* @param caught an exception caught serving the request.
- * @since 5.10
+ * @return wrapped response
*/
public static HttpServerResponse create(@Nullable HttpServletRequest request, HttpServletResponse response,
@Nullable Throwable caught) {
diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionBuilderCustomizerTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionBuilderCustomizerTests.java
new file mode 100644
index 000000000..ab5f3725a
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionBuilderCustomizerTests.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import com.datastax.oss.driver.api.core.CqlSessionBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.mock;
+
+class TraceCqlSessionBuilderCustomizerTests {
+
+ @Test
+ void should_register_trace_request_tracker() {
+ TraceCqlSessionBuilderCustomizer customizer = new TraceCqlSessionBuilderCustomizer();
+ CqlSessionBuilder builder = mock(CqlSessionBuilder.class);
+
+ customizer.customize(builder);
+
+ then(builder).should().withRequestTracker(isA(TraceRequestTracker.class));
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionInterceptorTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionInterceptorTests.java
new file mode 100644
index 000000000..fcba0a9b0
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceCqlSessionInterceptorTests.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import java.util.Optional;
+import java.util.function.BiConsumer;
+
+import com.datastax.oss.driver.api.core.CqlIdentifier;
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.cql.AsyncCqlSession;
+import com.datastax.oss.driver.api.core.cql.SimpleStatement;
+import com.datastax.oss.driver.api.core.cql.SyncCqlSession;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.support.StaticListableBeanFactory;
+import org.springframework.cloud.sleuth.tracer.SimpleCurrentTraceContext;
+import org.springframework.cloud.sleuth.tracer.SimpleSpan;
+import org.springframework.cloud.sleuth.tracer.SimpleTracer;
+
+import static org.assertj.core.api.BDDAssertions.then;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+class TraceCqlSessionInterceptorTests {
+
+ SimpleTracer simpleTracer = new SimpleTracer();
+
+ SimpleCurrentTraceContext simpleCurrentTraceContext = SimpleCurrentTraceContext.withTracer(this.simpleTracer);
+
+ BeanFactory beanFactory = beanFactory();
+
+ @Test
+ void should_register_a_span_for_execute() {
+ assertThatTracingWorks(SyncCqlSession::execute);
+ }
+
+ @Test
+ void should_register_a_span_for_executeAsync() {
+ assertThatTracingWorks(AsyncCqlSession::executeAsync);
+ }
+
+ @Test
+ void should_register_a_span_for_prepare() {
+ assertThatTracingWorks(SyncCqlSession::prepare);
+ }
+
+ private void assertThatTracingWorks(BiConsumer consumer) {
+ CqlSession session = proxied(mock(CqlSession.class));
+ SimpleStatement statement = mock(SimpleStatement.class);
+ given(statement.getQuery())
+ .willReturn("Insert into University.Student(RollNo,Name,dept,Semester) values(2,'Michael','CS', 2);");
+
+ consumer.accept(session, statement);
+
+ SimpleSpan span = this.simpleTracer.getLastSpan();
+ then(span).isNotNull();
+ then(span.tags).containsKeys(SleuthCassandraSpan.Tags.CQL_TAG.getKey(),
+ SleuthCassandraSpan.Tags.KEYSPACE_NAME.getKey());
+ then(span.remoteServiceName).isNotBlank();
+ }
+
+ private BeanFactory beanFactory() {
+ StaticListableBeanFactory beanFactory = new StaticListableBeanFactory();
+ beanFactory.addBean("tracer", this.simpleTracer);
+ beanFactory.addBean("currentTraceContext", this.simpleCurrentTraceContext);
+ return beanFactory;
+ }
+
+ private CqlSession proxied(CqlSession session) {
+ ProxyFactory proxyFactory = new ProxyFactory();
+ proxyFactory.setTarget(session);
+ proxyFactory.addAdvice(new TraceCqlSessionInterceptor(session, this.beanFactory) {
+ @Override
+ boolean isContextUnusable() {
+ return false;
+ }
+
+ @Override
+ String getSessionName() {
+ return "test";
+ }
+
+ @Override
+ Optional getKeyspace() {
+ return Optional.empty();
+ }
+ });
+ proxyFactory.addInterface(CqlSession.class);
+ return (CqlSession) proxyFactory.getProxy();
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceReactiveSessionTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceReactiveSessionTests.java
new file mode 100644
index 000000000..20f6f44f1
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceReactiveSessionTests.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.Optional;
+
+import com.datastax.oss.driver.api.core.ProtocolVersion;
+import com.datastax.oss.driver.api.core.addresstranslation.AddressTranslator;
+import com.datastax.oss.driver.api.core.auth.AuthProvider;
+import com.datastax.oss.driver.api.core.config.DriverConfig;
+import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
+import com.datastax.oss.driver.api.core.connection.ReconnectionPolicy;
+import com.datastax.oss.driver.api.core.context.DriverContext;
+import com.datastax.oss.driver.api.core.cql.SimpleStatement;
+import com.datastax.oss.driver.api.core.cql.Statement;
+import com.datastax.oss.driver.api.core.loadbalancing.LoadBalancingPolicy;
+import com.datastax.oss.driver.api.core.metadata.NodeStateListener;
+import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener;
+import com.datastax.oss.driver.api.core.retry.RetryPolicy;
+import com.datastax.oss.driver.api.core.session.throttling.RequestThrottler;
+import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy;
+import com.datastax.oss.driver.api.core.ssl.SslEngineFactory;
+import com.datastax.oss.driver.api.core.time.TimestampGenerator;
+import com.datastax.oss.driver.api.core.tracker.RequestTracker;
+import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
+import org.junit.jupiter.api.Test;
+import reactor.core.publisher.Mono;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.support.StaticListableBeanFactory;
+import org.springframework.cloud.sleuth.tracer.SimpleCurrentTraceContext;
+import org.springframework.cloud.sleuth.tracer.SimpleSpan;
+import org.springframework.cloud.sleuth.tracer.SimpleTracer;
+import org.springframework.data.cassandra.ReactiveSession;
+import org.springframework.lang.NonNull;
+
+import static org.assertj.core.api.BDDAssertions.then;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+class TraceReactiveSessionTests {
+
+ SimpleTracer simpleTracer = new SimpleTracer();
+
+ SimpleCurrentTraceContext simpleCurrentTraceContext = SimpleCurrentTraceContext.withTracer(this.simpleTracer);
+
+ @Test
+ void should_register_a_span_for_execute() {
+ ReactiveSession session = mock(ReactiveSession.class);
+ given(session.execute(any(Statement.class))).willReturn(Mono.empty());
+ SimpleStatement statement = mock(SimpleStatement.class);
+ given(statement.getQuery())
+ .willReturn("Insert into University.Student(RollNo,Name,dept,Semester) values(2,'Michael','CS', 2);");
+
+ new TraceReactiveSession(session, beanFactory()) {
+ @Override
+ public DriverContext getContext() {
+ return driverContext();
+ }
+ }.execute(statement).block(Duration.ofMillis(10));
+
+ SimpleSpan span = this.simpleTracer.getLastSpan();
+ then(span).isNotNull();
+ then(span.tags).containsKeys(SleuthCassandraSpan.Tags.CQL_TAG.getKey(),
+ SleuthCassandraSpan.Tags.KEYSPACE_NAME.getKey());
+ then(span.remoteServiceName).isNotBlank();
+ }
+
+ private BeanFactory beanFactory() {
+ StaticListableBeanFactory beanFactory = new StaticListableBeanFactory();
+ beanFactory.addBean("tracer", this.simpleTracer);
+ beanFactory.addBean("currentTraceContext", this.simpleCurrentTraceContext);
+ return beanFactory;
+ }
+
+ private DriverContext driverContext() {
+ return new DriverContext() {
+ @NonNull
+ @Override
+ public String getSessionName() {
+ return "session";
+ }
+
+ @NonNull
+ @Override
+ public DriverConfig getConfig() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public DriverConfigLoader getConfigLoader() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Map getLoadBalancingPolicies() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Map getRetryPolicies() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Map getSpeculativeExecutionPolicies() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public TimestampGenerator getTimestampGenerator() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public ReconnectionPolicy getReconnectionPolicy() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public AddressTranslator getAddressTranslator() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Optional getAuthProvider() {
+ return Optional.empty();
+ }
+
+ @NonNull
+ @Override
+ public Optional getSslEngineFactory() {
+ return Optional.empty();
+ }
+
+ @NonNull
+ @Override
+ public RequestTracker getRequestTracker() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public RequestThrottler getRequestThrottler() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public NodeStateListener getNodeStateListener() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public SchemaChangeListener getSchemaChangeListener() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public ProtocolVersion getProtocolVersion() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public CodecRegistry getCodecRegistry() {
+ return null;
+ }
+ };
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceRequestTrackerTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceRequestTrackerTests.java
new file mode 100644
index 000000000..ada2dcc3c
--- /dev/null
+++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/cassandra/TraceRequestTrackerTests.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2013-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.springframework.cloud.sleuth.instrument.cassandra;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.util.Map;
+
+import com.datastax.oss.driver.api.core.CqlIdentifier;
+import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
+import com.datastax.oss.driver.api.core.metadata.EndPoint;
+import com.datastax.oss.driver.api.core.metadata.Node;
+import com.datastax.oss.driver.api.core.metadata.token.Token;
+import com.datastax.oss.driver.api.core.session.Request;
+import org.junit.jupiter.api.Test;
+import org.mockito.BDDMockito;
+
+import org.springframework.cloud.sleuth.Span;
+import org.springframework.cloud.sleuth.tracer.SimpleSpan;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+import static org.assertj.core.api.BDDAssertions.then;
+
+class TraceRequestTrackerTests {
+
+ @Test
+ void should_end_span_on_success() {
+ TraceRequestTracker traceRequestTracker = TraceRequestTracker.INSTANCE;
+ SimpleSpan span = new SimpleSpan();
+
+ traceRequestTracker.onSuccess(new MyRequest(span), 1L, BDDMockito.mock(DriverExecutionProfile.class),
+ BDDMockito.mock(Node.class), "");
+
+ then(span.ended).isTrue();
+ }
+
+ @Test
+ void should_end_span_on_error() {
+ TraceRequestTracker traceRequestTracker = TraceRequestTracker.INSTANCE;
+ SimpleSpan span = new SimpleSpan();
+
+ traceRequestTracker.onError(new MyRequest(span), new IllegalStateException("Foo"), 1L,
+ BDDMockito.mock(DriverExecutionProfile.class), BDDMockito.mock(Node.class), "");
+
+ then(span.ended).isTrue();
+ then(span.throwable).isNotNull();
+ }
+
+ @Test
+ void should_customize_span_on_node_error() {
+ TraceRequestTracker traceRequestTracker = TraceRequestTracker.INSTANCE;
+ SimpleSpan span = new SimpleSpan();
+ Node node = BDDMockito.mock(Node.class);
+ BDDMockito.given(node.getEndPoint()).willReturn(endpoint());
+
+ traceRequestTracker.onNodeError(new MyRequest(span), new IllegalStateException("Foo"), 1L,
+ BDDMockito.mock(DriverExecutionProfile.class), node, "");
+
+ then(span.events).contains(SleuthCassandraSpan.Events.NODE_ERROR.getValue());
+ then(span.tags).containsEntry("cassandra.node[localhost/127.0.0.1:1234].error",
+ "java.lang.IllegalStateException: Foo");
+ then(span.ip).isNotEmpty();
+ then(span.port).isPositive();
+ }
+
+ @Test
+ void should_customize_span_on_node_success() {
+ TraceRequestTracker traceRequestTracker = TraceRequestTracker.INSTANCE;
+ SimpleSpan span = new SimpleSpan();
+ Node node = BDDMockito.mock(Node.class);
+ BDDMockito.given(node.getEndPoint()).willReturn(endpoint());
+
+ traceRequestTracker.onNodeSuccess(new MyRequest(span), 1L, BDDMockito.mock(DriverExecutionProfile.class), node,
+ "");
+
+ then(span.events).contains(SleuthCassandraSpan.Events.NODE_SUCCESS.getValue());
+ then(span.ip).isNotEmpty();
+ then(span.port).isPositive();
+ }
+
+ private EndPoint endpoint() {
+ return new EndPoint() {
+ @NonNull
+ @Override
+ public SocketAddress resolve() {
+ return new InetSocketAddress("localhost", 1234);
+ }
+
+ @NonNull
+ @Override
+ public String asMetricPrefix() {
+ return "";
+ }
+
+ @Override
+ public String toString() {
+ return resolve().toString();
+ }
+ };
+ }
+
+}
+
+class MyRequest implements Request, CassandraSpanSupplier {
+
+ private final Span span;
+
+ MyRequest(Span span) {
+ this.span = span;
+ }
+
+ @Nullable
+ @Override
+ public String getExecutionProfileName() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public DriverExecutionProfile getExecutionProfile() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public CqlIdentifier getKeyspace() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public CqlIdentifier getRoutingKeyspace() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public ByteBuffer getRoutingKey() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Token getRoutingToken() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public Map getCustomPayload() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Boolean isIdempotent() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Duration getTimeout() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Node getNode() {
+ return null;
+ }
+
+ @Override
+ public Span getSpan() {
+ return this.span;
+ }
+
+}
diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleCurrentTraceContext.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleCurrentTraceContext.java
index 6c171a1d2..0c3acd5f9 100644
--- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleCurrentTraceContext.java
+++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleCurrentTraceContext.java
@@ -72,4 +72,13 @@ public class SimpleCurrentTraceContext implements CurrentTraceContext {
return delegate;
}
+ public static SimpleCurrentTraceContext withTracer(SimpleTracer simpleTracer) {
+ return new SimpleCurrentTraceContext() {
+ @Override
+ public TraceContext context() {
+ return simpleTracer.currentSpan() != null ? simpleTracer.currentSpan().context() : null;
+ }
+ };
+ }
+
}
diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleSpan.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleSpan.java
index c4213fd4b..7df1f7035 100644
--- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleSpan.java
+++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleSpan.java
@@ -48,6 +48,10 @@ public class SimpleSpan implements Span {
public String name;
+ public String ip;
+
+ public int port;
+
@Override
public boolean isNoop() {
return true;
@@ -88,6 +92,13 @@ public class SimpleSpan implements Span {
return this;
}
+ @Override
+ public Span remoteIpAndPort(String ip, int port) {
+ this.ip = ip;
+ this.port = port;
+ return this;
+ }
+
@Override
public void end() {
this.ended = true;
diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-baggage-tests/src/test/java/org/springframework/cloud/sleuth/brave/baggage/MultipleHopsIntegrationTests.java b/tests/brave/spring-cloud-sleuth-instrumentation-baggage-tests/src/test/java/org/springframework/cloud/sleuth/brave/baggage/MultipleHopsIntegrationTests.java
index 300ff616a..8292a235f 100644
--- a/tests/brave/spring-cloud-sleuth-instrumentation-baggage-tests/src/test/java/org/springframework/cloud/sleuth/brave/baggage/MultipleHopsIntegrationTests.java
+++ b/tests/brave/spring-cloud-sleuth-instrumentation-baggage-tests/src/test/java/org/springframework/cloud/sleuth/brave/baggage/MultipleHopsIntegrationTests.java
@@ -21,6 +21,7 @@ import brave.baggage.BaggagePropagationConfig;
import brave.sampler.Sampler;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
@@ -75,9 +76,9 @@ public class MultipleHopsIntegrationTests
}
@Configuration(proxyBeanMethods = false)
- @EnableAutoConfiguration(
- exclude = { MongoAutoConfiguration.class, QuartzAutoConfiguration.class, JmxAutoConfiguration.class,
- R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
+ @EnableAutoConfiguration(exclude = { MongoAutoConfiguration.class, QuartzAutoConfiguration.class,
+ JmxAutoConfiguration.class, R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class,
+ RedisAutoConfiguration.class, CassandraAutoConfiguration.class })
static class Config {
@Bean
diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/JmsTracingConfigurationTest.java b/tests/brave/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/JmsTracingConfigurationTest.java
index f4a41ed5c..d6912f647 100644
--- a/tests/brave/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/JmsTracingConfigurationTest.java
+++ b/tests/brave/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/JmsTracingConfigurationTest.java
@@ -35,6 +35,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
@@ -169,9 +170,9 @@ public class JmsTracingConfigurationTest {
}
@Configuration(proxyBeanMethods = false)
-@EnableAutoConfiguration(
- exclude = { KafkaAutoConfiguration.class, MongoAutoConfiguration.class, QuartzAutoConfiguration.class,
- R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
+@EnableAutoConfiguration(exclude = { KafkaAutoConfiguration.class, MongoAutoConfiguration.class,
+ QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class,
+ RedisAutoConfiguration.class, CassandraAutoConfiguration.class })
class JmsTestTracingConfiguration {
}
diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/issues/issue_943/HelloSpringIntegration.java b/tests/brave/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/issues/issue_943/HelloSpringIntegration.java
index a51599cfd..5e449776b 100644
--- a/tests/brave/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/issues/issue_943/HelloSpringIntegration.java
+++ b/tests/brave/spring-cloud-sleuth-instrumentation-messaging-tests/src/test/java/org/springframework/cloud/sleuth/instrument/messaging/issues/issue_943/HelloSpringIntegration.java
@@ -22,6 +22,7 @@ import brave.test.TestSpanHandler;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@@ -34,7 +35,8 @@ import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
- R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
+ R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class,
+ CassandraAutoConfiguration.class })
@ImportResource("classpath:beans/applicationContext.xml")
@EnableIntegration
@EnableAsync