Adds Cassandra support (#1974)
Co-authored-by: Mark Paluch <mpaluch@vmware.com>
This commit is contained in:
committed by
GitHub
parent
209ce7d8ec
commit
17ba1b18d5
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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,\
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user