Adds Cassandra support (#1974)

Co-authored-by: Mark Paluch <mpaluch@vmware.com>
This commit is contained in:
Marcin Grzejszczak
2021-06-10 15:46:04 +02:00
committed by GitHub
parent 209ce7d8ec
commit 17ba1b18d5
46 changed files with 1916 additions and 19 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -123,6 +123,16 @@
<artifactId>spring-cloud-starter-task</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra-reactive</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-spi</artifactId>

View File

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

View File

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

View File

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

View File

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

View File

@@ -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",

View File

@@ -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,\

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -167,6 +167,16 @@
<artifactId>spring-cloud-starter-task</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra-reactive</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-deployer-spi</artifactId>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Statement<?>, 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<CqlIdentifier> 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<CqlIdentifier> 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;
}
}

View File

@@ -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<ReactiveResultSet> execute(String cql) {
return execute(SimpleStatement.newInstance(cql));
}
@Override
public Mono<ReactiveResultSet> execute(String cql, Object... objects) {
return execute(SimpleStatement.newInstance(cql, objects));
}
@Override
public Mono<ReactiveResultSet> execute(String cql, Map<String, Object> map) {
return execute(SimpleStatement.newInstance(cql, map));
}
@Override
public Mono<ReactiveResultSet> 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<PreparedStatement> prepare(String cql) {
return prepare(SimpleStatement.newInstance(cql));
}
@Override
public Mono<PreparedStatement> 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;
}
}

View File

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

View File

@@ -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 <T> type of target
* @return proxied object with trace advice
*/
@SuppressWarnings("unchecked")
public static <T> 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;
}
}

View File

@@ -30,6 +30,7 @@ public interface TraceListenerStrategySpanCustomizer<T extends CommonDataSource>
/**
* 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);

View File

@@ -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<T> implements Subscription, CoreSubscriber<T>, Scannable {

View File

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

View File

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

View File

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

View File

@@ -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<CqlSession, SimpleStatement> 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<CqlIdentifier> getKeyspace() {
return Optional.empty();
}
});
proxyFactory.addInterface(CqlSession.class);
return (CqlSession) proxyFactory.getProxy();
}
}

View File

@@ -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<String, LoadBalancingPolicy> getLoadBalancingPolicies() {
return null;
}
@NonNull
@Override
public Map<String, RetryPolicy> getRetryPolicies() {
return null;
}
@NonNull
@Override
public Map<String, SpeculativeExecutionPolicy> 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<AuthProvider> getAuthProvider() {
return Optional.empty();
}
@NonNull
@Override
public Optional<SslEngineFactory> 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;
}
};
}
}

View File

@@ -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<String, ByteBuffer> 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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