diff --git a/docs/src/main/asciidoc/integrations.adoc b/docs/src/main/asciidoc/integrations.adoc index 9a67a7807..c710467bc 100644 --- a/docs/src/main/asciidoc/integrations.adoc +++ b/docs/src/main/asciidoc/integrations.adoc @@ -599,3 +599,11 @@ This feature is available for all tracer implementations. If you have Spring Batch running on the classpath, we wrap the `StepBuilderFactory` and the `JobBuilderFactory` to propagate the tracing context. In order to disable this instrumentation set `spring.sleuth.batch.enabled` to `false`. + +[[sleuth-tx-integration]] +== Spring Tx + +This feature is available for all tracer implementations. + +If you have Spring Tx on the classpath we will instrument the `PlatformTransactionManager` and the `ReactiveTransactionManager` to create a span whenever a new transaction is created. +In order to disable this instrumentation set `spring.sleuth.tx.enabled` to `false`. diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceTaskAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceTaskAutoConfiguration.java index 591ca5941..52bf73685 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceTaskAutoConfiguration.java +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceTaskAutoConfiguration.java @@ -30,7 +30,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** - * Registers beans related to Spring Cloud Task scheduling. + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} that registers instrumentation for Spring Cloud Task. * * @author Marcin Grzejszczak * @since 3.1.0 diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TracePlatformTransactionManagerBeanPostProcessor.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TracePlatformTransactionManagerBeanPostProcessor.java new file mode 100644 index 000000000..7ecb647dc --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TracePlatformTransactionManagerBeanPostProcessor.java @@ -0,0 +1,47 @@ +/* + * 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.tx; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.sleuth.instrument.tx.TracePlatformTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * Post processor that wraps a {@link PlatformTransactionManager}. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TracePlatformTransactionManagerBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + + public TracePlatformTransactionManagerBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof PlatformTransactionManager && !(bean instanceof TracePlatformTransactionManager)) { + return new TracePlatformTransactionManager((PlatformTransactionManager) bean, this.beanFactory); + } + return bean; + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TraceReactiveTransactionManagerBeanPostProcessor.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TraceReactiveTransactionManagerBeanPostProcessor.java new file mode 100644 index 000000000..8a7ac5510 --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TraceReactiveTransactionManagerBeanPostProcessor.java @@ -0,0 +1,47 @@ +/* + * 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.tx; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.cloud.sleuth.instrument.tx.TraceReactiveTransactionManager; +import org.springframework.transaction.ReactiveTransactionManager; + +/** + * Post processor that wraps a {@link ReactiveTransactionManager}. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TraceReactiveTransactionManagerBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + + public TraceReactiveTransactionManagerBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof ReactiveTransactionManager && !(bean instanceof TraceReactiveTransactionManager)) { + return new TraceReactiveTransactionManager((ReactiveTransactionManager) bean, this.beanFactory); + } + return bean; + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TraceTxAutoConfiguration.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TraceTxAutoConfiguration.java new file mode 100644 index 000000000..f1fd9b189 --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TraceTxAutoConfiguration.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.autoconfig.instrument.tx; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} that registers instrumentation for Spring TX. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(value = "spring.sleuth.tx.enabled", matchIfMissing = true) +@ConditionalOnBean(Tracer.class) +@AutoConfigureAfter(BraveAutoConfiguration.class) +public class TraceTxAutoConfiguration { + + @Bean + @ConditionalOnClass(name = "org.springframework.transaction.PlatformTransactionManager") + static TracePlatformTransactionManagerBeanPostProcessor tracePlatformTransactionManagerBeanPostProcessor( + BeanFactory beanFactory) { + return new TracePlatformTransactionManagerBeanPostProcessor(beanFactory); + } + + @Bean + @ConditionalOnClass( + name = { "org.springframework.transaction.ReactiveTransactionManager", "reactor.core.publisher.Mono" }) + static TraceReactiveTransactionManagerBeanPostProcessor traceReactiveTransactionManagerBeanPostProcessor( + BeanFactory beanFactory) { + return new TraceReactiveTransactionManagerBeanPostProcessor(beanFactory); + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index fef960fe4..c94fec07a 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -155,6 +155,12 @@ "description": "Enable Spring Cloud Config Server instrumentation.", "defaultValue": true }, + { + "name": "spring.sleuth.tx.enabled", + "type": "java.lang.Boolean", + "description": "Enable Spring TX instrumentation.", + "defaultValue": true + }, { "name": "spring.sleuth.batch.enabled", "type": "java.lang.Boolean", diff --git a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories index fff9ab35e..d91d9d53e 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories @@ -21,6 +21,7 @@ org.springframework.cloud.sleuth.autoconfig.instrument.messaging.TraceSpringInte org.springframework.cloud.sleuth.autoconfig.instrument.messaging.TraceSpringMessagingAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.instrument.messaging.TraceWebSocketAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.instrument.rsocket.TraceRSocketAutoConfiguration, \ +org.springframework.cloud.sleuth.autoconfig.instrument.tx.TraceTxAutoConfiguration, \ org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.brave.instrument.web.client.BraveWebClientAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.brave.instrument.rpc.BraveRpcAutoConfiguration,\ diff --git a/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TraceTxAutoConfigurationTests.java b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TraceTxAutoConfigurationTests.java new file mode 100644 index 000000000..04f3e5acd --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/test/java/org/springframework/cloud/sleuth/autoconfig/instrument/tx/TraceTxAutoConfigurationTests.java @@ -0,0 +1,63 @@ +/* + * 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.tx; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +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.transaction.PlatformTransactionManager; +import org.springframework.transaction.ReactiveTransactionManager; + +class TraceTxAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.sleuth.noop.enabled=true") + .withConfiguration(AutoConfigurations.of(TraceNoOpAutoConfiguration.class, TraceTxAutoConfiguration.class)); + + @Test + void should_register_bean_post_processors() { + this.contextRunner.run(context -> Assertions.assertThat(context) + .hasSingleBean(TracePlatformTransactionManagerBeanPostProcessor.class) + .hasSingleBean(TraceReactiveTransactionManagerBeanPostProcessor.class)); + } + + @Test + void should_not_register_bean_post_processor_when_tx_not_on_classpath() { + this.contextRunner.withClassLoader(new FilteredClassLoader(PlatformTransactionManager.class)) + .run(context -> Assertions.assertThat(context) + .doesNotHaveBean(TracePlatformTransactionManagerBeanPostProcessor.class)); + } + + @Test + void should_not_register_reactive_bean_post_processor_when_reactive_tx_not_on_classpath() { + this.contextRunner.withClassLoader(new FilteredClassLoader(ReactiveTransactionManager.class)) + .run(context -> Assertions.assertThat(context) + .doesNotHaveBean(TraceReactiveTransactionManagerBeanPostProcessor.class)); + } + + @Test + void should_not_register_reactive_bean_post_processor_when_reactor_not_on_classpath() { + this.contextRunner.withClassLoader(new FilteredClassLoader(Mono.class)).run(context -> Assertions + .assertThat(context).doesNotHaveBean(TraceReactiveTransactionManagerBeanPostProcessor.class)); + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployer.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployer.java index b4f2febe5..6e2ba7e38 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployer.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployer.java @@ -129,13 +129,14 @@ public class TraceAppDeployer implements AppDeployer { @Override public Mono statusReactive(String id) { return ReactorSleuth.tracedMono(tracer(), currentTraceContext(), "status", - () -> this.delegate.statusReactive(id), span -> span.tag("deployer.app.id", id)); + () -> this.delegate.statusReactive(id), (o, span) -> span.tag("deployer.app.id", id)); } @Override public Flux statusesReactive(String... ids) { return ReactorSleuth.tracedFlux(tracer(), currentTraceContext(), "statuses", - () -> this.delegate.statusesReactive(ids), span -> span.tag("deployer.app.ids", Arrays.toString(ids))); + () -> this.delegate.statusesReactive(ids), + (o, span) -> span.tag("deployer.app.ids", Arrays.toString(ids))); } @Override diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java index 02d870027..45965a763 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/reactor/ReactorSleuth.java @@ -16,8 +16,8 @@ package org.springframework.cloud.sleuth.instrument.reactor; +import java.util.function.BiConsumer; import java.util.function.BiFunction; -import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; @@ -333,18 +333,21 @@ public abstract class ReactorSleuth { */ public static Mono tracedMono(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext, @NonNull String childSpanName, @NonNull Supplier> supplier, - @NonNull Consumer spanCustomizer) { + @NonNull BiConsumer spanCustomizer) { return runMonoSupplierInScope(supplier, spanCustomizer).contextWrite( context -> ReactorSleuth.enhanceContext(tracer, currentTraceContext, context, childSpanName)); } - private static Mono runMonoSupplierInScope(Supplier> supplier, Consumer spanCustomizer) { + private static Mono runMonoSupplierInScope(Supplier> supplier, BiConsumer spanCustomizer) { return Mono.deferContextual(contextView -> { Span span = contextView.get(Span.class); - spanCustomizer.accept(span); Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class); // @formatter:off return supplier.get() + .map(t -> { + spanCustomizer.accept(t, span); + return t; + }) // TODO: Fix me when this is resolved in Reactor // .doOnSubscribe(__ -> scope.close()) .doOnError(span::error) @@ -368,7 +371,7 @@ public abstract class ReactorSleuth { */ public static Mono tracedMono(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext, @NonNull String childSpanName, @NonNull Supplier> supplier) { - return tracedMono(tracer, currentTraceContext, childSpanName, supplier, span -> { + return tracedMono(tracer, currentTraceContext, childSpanName, supplier, (o, span) -> { }); } @@ -382,7 +385,7 @@ public abstract class ReactorSleuth { */ public static Mono tracedMono(@NonNull Tracer tracer, @NonNull Span span, @NonNull Supplier> supplier) { - return runMonoSupplierInScope(supplier, span1 -> { + return runMonoSupplierInScope(supplier, (o, span1) -> { }).contextWrite(context -> ReactorSleuth.putSpanInScope(tracer, context, span)); } @@ -399,7 +402,7 @@ public abstract class ReactorSleuth { */ public static Flux tracedFlux(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext, @NonNull String childSpanName, @NonNull Supplier> supplier, - @NonNull Consumer spanCustomizer) { + @NonNull BiConsumer spanCustomizer) { return runFluxSupplierInScope(supplier, spanCustomizer).contextWrite( context -> ReactorSleuth.enhanceContext(tracer, currentTraceContext, context, childSpanName)); } @@ -415,17 +418,20 @@ public abstract class ReactorSleuth { */ public static Flux tracedFlux(@NonNull Tracer tracer, @NonNull Span span, @NonNull Supplier> supplier) { - return runFluxSupplierInScope(supplier, span1 -> { + return runFluxSupplierInScope(supplier, (o, span1) -> { }).contextWrite(context -> ReactorSleuth.putSpanInScope(tracer, context, span)); } - private static Flux runFluxSupplierInScope(Supplier> supplier, Consumer spanCustomizer) { + private static Flux runFluxSupplierInScope(Supplier> supplier, BiConsumer spanCustomizer) { return Flux.deferContextual(contextView -> { Span span = contextView.get(Span.class); - spanCustomizer.accept(span); Tracer.SpanInScope scope = contextView.get(Tracer.SpanInScope.class); // @formatter:off return supplier.get() + .map(t -> { + spanCustomizer.accept(t, span); + return t; + }) // TODO: Fix me when this is resolved in Reactor // .doOnSubscribe(__ -> scope.close()) .doOnError(span::error) @@ -449,24 +455,21 @@ public abstract class ReactorSleuth { */ public static Flux tracedFlux(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext, @NonNull String childSpanName, @NonNull Supplier> supplier) { - return tracedFlux(tracer, currentTraceContext, childSpanName, supplier, span -> { + return tracedFlux(tracer, currentTraceContext, childSpanName, supplier, (o, span) -> { }); } - private static Span spanFromContext(Tracer tracer, CurrentTraceContext currentTraceContext, + private static Span childSpanFromContext(Tracer tracer, CurrentTraceContext currentTraceContext, reactor.util.context.Context context, String childSpanName) { TraceContext traceContext = context.getOrDefault(TraceContext.class, null); - Span span = null; - if (traceContext == null) { - span = context.getOrDefault(Span.class, null); - } + Span span = context.getOrDefault(Span.class, null); if (traceContext == null && span == null) { span = tracer.nextSpan(); if (log.isDebugEnabled()) { log.debug("There was no previous span in reactor context, created a new one [" + span + "]"); } } - else if (traceContext != null) { + else if (traceContext != null && span == null) { // there was a previous span - we create a child one try (CurrentTraceContext.Scope scope = currentTraceContext.maybeScope(traceContext)) { if (log.isDebugEnabled()) { @@ -490,9 +493,19 @@ public abstract class ReactorSleuth { return span.name(childSpanName).start(); } - private static Context enhanceContext(Tracer tracer, CurrentTraceContext currentTraceContext, + /** + * 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 + * @return updated Reactor context + */ + public static Context enhanceContext(Tracer tracer, CurrentTraceContext currentTraceContext, reactor.util.context.Context context, String childSpanName) { - Span span = spanFromContext(tracer, currentTraceContext, context, childSpanName); + Span span = childSpanFromContext(tracer, currentTraceContext, context, childSpanName); return putSpanInScope(tracer, context, span); } diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/tx/TracePlatformTransactionManager.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/tx/TracePlatformTransactionManager.java new file mode 100644 index 000000000..e2d60723c --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/tx/TracePlatformTransactionManager.java @@ -0,0 +1,173 @@ +/* + * 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.tx; + +import javax.annotation.PostConstruct; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanAndScope; +import org.springframework.cloud.sleuth.ThreadLocalSpan; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionStatus; + +/** + * A trace representation of a {@link PlatformTransactionManager}. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TracePlatformTransactionManager implements PlatformTransactionManager { + + private static final Log log = LogFactory.getLog(TracePlatformTransactionManager.class); + + private final PlatformTransactionManager delegate; + + private final BeanFactory beanFactory; + + private Tracer tracer; + + volatile ThreadLocalSpan threadLocalSpan; + + public TracePlatformTransactionManager(PlatformTransactionManager delegate, BeanFactory beanFactory) { + this.delegate = delegate; + this.beanFactory = beanFactory; + } + + @PostConstruct + void initialize() { + if (this.threadLocalSpan == null) { + this.threadLocalSpan = new ThreadLocalSpan(tracer()); + } + } + + @Override + public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { + initialize(); + SpanAndScope spanAndScope = this.threadLocalSpan.get(); + Span currentSpan = spanAndScope != null ? spanAndScope.getSpan() : tracer().currentSpan(); + Span span = fallbackSpan(); + try { + TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults()); + TransactionStatus status = this.delegate.getTransaction(definition); + span = taggedSpan(currentSpan, span, def, status); + return status; + } + catch (Exception e) { + if (log.isDebugEnabled()) { + log.debug( + "Exception occurred while trying to get a transaction, will mark the span with error and report it"); + } + span.error(e); + span.end(); + throw e; + } + } + + Span fallbackSpan() { + return tracer().nextSpan().name("tx").start(); + } + + private Span taggedSpan(Span currentSpan, Span span, TransactionDefinition def, TransactionStatus status) { + if (status.isNewTransaction() || currentSpan == null) { + log.info("Creating new span cause a new transaction is started"); + TracePlatformTransactionManagerTags.tag(span, def, this.delegate.getClass()); + } + else { + span = currentSpan; + } + this.threadLocalSpan.set(span); + return span; + } + + @Override + public void commit(TransactionStatus status) throws TransactionException { + SpanAndScope spanAndScope = this.threadLocalSpan.get(); + if (spanAndScope == null) { + if (log.isDebugEnabled()) { + log.debug("No span and scope found - this shouldn't happen, sth is wrong"); + } + this.delegate.commit(status); + return; + } + Exception ex = null; + Span span = spanAndScope.getSpan(); + try { + if (log.isDebugEnabled()) { + log.debug("Wrapping commit"); + } + this.delegate.commit(status); + } + catch (Exception e) { + ex = e; + span.error(e); + throw e; + } + finally { + span.event("tx commit"); + span.end(); + if (ex == null) { + if (log.isDebugEnabled()) { + log.debug("No exception was found - will clear thread local span"); + } + this.threadLocalSpan.remove(); + } + } + } + + @Override + public void rollback(TransactionStatus status) throws TransactionException { + SpanAndScope spanAndScope = this.threadLocalSpan.get(); + if (spanAndScope == null) { + if (log.isDebugEnabled()) { + log.debug("No span and scope found - this shouldn't happen, sth is wrong"); + } + this.delegate.rollback(status); + return; + } + Span span = spanAndScope.getSpan(); + try { + if (log.isDebugEnabled()) { + log.debug("Wrapping rollback"); + } + this.delegate.rollback(status); + } + catch (Exception e) { + span.error(e); + throw e; + } + finally { + span.event("tx rollback"); + span.end(); + this.threadLocalSpan.remove(); + } + } + + private Tracer tracer() { + if (this.tracer == null) { + this.tracer = this.beanFactory.getBean(Tracer.class); + } + return this.tracer; + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/tx/TracePlatformTransactionManagerTags.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/tx/TracePlatformTransactionManagerTags.java new file mode 100644 index 000000000..664c64b86 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/tx/TracePlatformTransactionManagerTags.java @@ -0,0 +1,81 @@ +/* + * 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.tx; + +import org.springframework.cloud.sleuth.Span; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +final class TracePlatformTransactionManagerTags { + + private TracePlatformTransactionManagerTags() { + throw new IllegalStateException("Can't instantiate a utility class"); + } + + static void tag(Span span, TransactionDefinition def, Class transactionManagerClass) { + span.tag("tx.transaction-manager", ClassUtils.getQualifiedName(transactionManagerClass)); + span.tag("tx.read-only", String.valueOf(def.isReadOnly())); + span.tag("tx.propagation-level", propagationLevel(def)); + span.tag("tx.isolation-level", isolationLevel(def)); + if (def.getTimeout() > 0) { + span.tag("tx.timeout", String.valueOf(def.getTimeout())); + } + if (StringUtils.hasText(def.getName())) { + span.tag("tx.name", def.getName()); + } + } + + private static String propagationLevel(TransactionDefinition def) { + switch (def.getPropagationBehavior()) { + case 0: + return "PROPAGATION_REQUIRED"; + case 1: + return "PROPAGATION_SUPPORTS"; + case 2: + return "PROPAGATION_MANDATORY"; + case 3: + return "PROPAGATION_REQUIRES_NEW"; + case 4: + return "PROPAGATION_NOT_SUPPORTED"; + case 5: + return "PROPAGATION_NEVER"; + case 6: + return "PROPAGATION_NESTED"; + default: + return String.valueOf(def.getPropagationBehavior()); + } + } + + private static String isolationLevel(TransactionDefinition def) { + switch (def.getIsolationLevel()) { + case -1: + return "ISOLATION_DEFAULT"; + case 1: + return "ISOLATION_READ_UNCOMMITTED"; + case 2: + return "ISOLATION_READ_COMMITTED"; + case 4: + return "ISOLATION_REPEATABLE_READ"; + case 8: + return "ISOLATION_SERIALIZABLE"; + default: + return String.valueOf(def.getIsolationLevel()); + } + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/tx/TraceReactiveTransactionManager.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/tx/TraceReactiveTransactionManager.java new file mode 100644 index 000000000..4a5f8fce2 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/tx/TraceReactiveTransactionManager.java @@ -0,0 +1,189 @@ +/* + * 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.tx; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import reactor.core.publisher.Mono; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.cloud.sleuth.CurrentTraceContext; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanAndScope; +import org.springframework.cloud.sleuth.TraceContext; +import org.springframework.cloud.sleuth.Tracer; +import org.springframework.transaction.ReactiveTransaction; +import org.springframework.transaction.ReactiveTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; + +/** + * A trace representation of a {@link ReactiveTransactionManager}. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TraceReactiveTransactionManager implements ReactiveTransactionManager { + + private static final Log log = LogFactory.getLog(TraceReactiveTransactionManager.class); + + private final ReactiveTransactionManager delegate; + + private final BeanFactory beanFactory; + + private Tracer tracer; + + private CurrentTraceContext currentTraceContext; + + public TraceReactiveTransactionManager(ReactiveTransactionManager delegate, BeanFactory beanFactory) { + this.delegate = delegate; + this.beanFactory = beanFactory; + } + + 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; + } + + @Override + public Mono getReactiveTransaction(TransactionDefinition definition) + throws TransactionException { + return Mono.deferContextual(contextView -> { + return this.delegate.getReactiveTransaction(definition).map(tx -> { + Span span = span(contextView); + if (tx.isNewTransaction() || span == null) { + if (span == null) { + span = tracer().nextSpan().name("tx").start(); + } + else { + span = tracer().nextSpan(span).name("tx").start(); + } + TracePlatformTransactionManagerTags.tag(span, definition, this.delegate.getClass()); + } + Tracer.SpanInScope withSpan = tracer().withSpan(span); + SpanAndScope spanAndScope = new SpanAndScope(span, withSpan); + return new TraceReactiveTransaction(tx, spanAndScope); + }); + }); + } + + private Span span(reactor.util.context.ContextView contextView) { + Span span = contextView.getOrDefault(Span.class, null); + if (span == null) { + TraceContext traceContext = contextView.getOrDefault(TraceContext.class, null); + if (traceContext == null) { + Span currentSpan = tracer().currentSpan(); + if (log.isDebugEnabled()) { + log.debug("There's no Span or TraceContext in the reactor context. Current span is [" + currentSpan + + "]"); + } + span = currentSpan; + } + else { + span = spanFromContext(traceContext); + } + } + return span; + } + + private Span spanFromContext(TraceContext traceContext) { + try (CurrentTraceContext.Scope scope = currentTraceContext.maybeScope(traceContext)) { + return tracer().currentSpan(); + } + } + + @Override + public Mono commit(ReactiveTransaction transaction) throws TransactionException { + if (!(transaction instanceof TraceReactiveTransaction)) { + return this.delegate.commit(transaction); + } + TraceReactiveTransaction reactiveTransaction = (TraceReactiveTransaction) transaction; + SpanAndScope spanAndScope = reactiveTransaction.spanAndScope; + Span span = spanAndScope.getSpan(); + Tracer.SpanInScope scope = spanAndScope.getScope(); + return this.delegate.commit(reactiveTransaction.delegate) + // TODO: Fix me when this is resolved in Reactor + // .doOnSubscribe(__ -> scope.close()) + .doOnError(span::error).doOnSuccess(signalType -> { + span.end(); + scope.close(); + }); + } + + @Override + public Mono rollback(ReactiveTransaction transaction) throws TransactionException { + if (!(transaction instanceof TraceReactiveTransaction)) { + return this.delegate.rollback(transaction); + } + TraceReactiveTransaction reactiveTransaction = (TraceReactiveTransaction) transaction; + SpanAndScope spanAndScope = reactiveTransaction.spanAndScope; + Span span = spanAndScope.getSpan(); + Tracer.SpanInScope scope = spanAndScope.getScope(); + return this.delegate.rollback(reactiveTransaction.delegate) + // TODO: Fix me when this is resolved in Reactor + // .doOnSubscribe(__ -> scope.close()) + .doOnError(span::error).doFinally(signalType -> { + span.end(); + if (scope != null) { + scope.close(); + } + }); + } + + static class TraceReactiveTransaction implements ReactiveTransaction { + + final ReactiveTransaction delegate; + + final SpanAndScope spanAndScope; + + TraceReactiveTransaction(ReactiveTransaction delegate, SpanAndScope spanAndScope) { + this.delegate = delegate; + this.spanAndScope = spanAndScope; + } + + @Override + public boolean isNewTransaction() { + return this.delegate.isNewTransaction(); + } + + @Override + public void setRollbackOnly() { + this.delegate.setRollbackOnly(); + } + + @Override + public boolean isRollbackOnly() { + return this.delegate.isRollbackOnly(); + } + + @Override + public boolean isCompleted() { + return this.delegate.isCompleted(); + } + + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java index 4f798c4c7..a4f49c81b 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/OkHttpFeignClientBeanPostProcessor.java @@ -29,6 +29,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor; * @author Marcin Grzejszczak * @since 1.1.3 */ +// TODO: Move this to autoconfigure public class OkHttpFeignClientBeanPostProcessor implements BeanPostProcessor { private final BeanFactory beanFactory; diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/ArchitectureTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/ArchitectureTests.java index 53b9421e7..860718387 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/ArchitectureTests.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/ArchitectureTests.java @@ -27,6 +27,8 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; @AnalyzeClasses(packagesOf = ArchitectureTests.class, importOptions = ArchitectureTests.ProductionCode.class) public class ArchitectureTests { + // TODO: Add "..org.springframework.beans.factory.config.." - BeanPostProcessors + // should end up in [autoconfig] @ArchTest public static final ArchRule should_not_contain_any_spring_configuration_reference_in_module = noClasses().should() .dependOnClassesThat().resideInAnyPackage("..org.springframework.boot.context.properties..", diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java index c6b50b62e..008ff5b59 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/TraceAppDeployerTests.java @@ -34,6 +34,8 @@ import org.springframework.cloud.deployer.spi.app.AppScaleRequest; import org.springframework.cloud.deployer.spi.app.AppStatus; import org.springframework.cloud.deployer.spi.core.AppDefinition; import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; +import org.springframework.cloud.sleuth.tracer.NoOpCurrentTraceContext; +import org.springframework.cloud.sleuth.tracer.SimpleTracer; import org.springframework.core.env.Environment; import org.springframework.core.io.PathResource; import org.springframework.mock.env.MockEnvironment; diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/tx/TracePlatformTransactionManagerTests.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/tx/TracePlatformTransactionManagerTests.java new file mode 100644 index 000000000..b9c623841 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/tx/TracePlatformTransactionManagerTests.java @@ -0,0 +1,201 @@ +/* + * 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.tx; + +import org.assertj.core.api.BDDAssertions; +import org.junit.jupiter.api.Test; +import org.mockito.BDDMockito; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.support.StaticListableBeanFactory; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.SpanAndScope; +import org.springframework.cloud.sleuth.tracer.SimpleSpan; +import org.springframework.cloud.sleuth.tracer.SimpleTracer; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.TransactionTimedOutException; +import org.springframework.transaction.support.SimpleTransactionStatus; + +import static org.assertj.core.api.BDDAssertions.then; + +class TracePlatformTransactionManagerTests { + + SimpleTracer tracer = new SimpleTracer(); + + PlatformTransactionManager delegate = BDDMockito.mock(PlatformTransactionManager.class); + + @Test + void should_create_a_new_span_for_a_new_committed_transaction() { + // given + TracePlatformTransactionManager manager = manager(); + setupTransactionStatusWithNewTransactionStatusEqualTo(true); + thenThreadLocalIsClear(manager); + + // when + TransactionStatus transaction = manager.getTransaction(null); + // then + SimpleSpan span = thenATaggedSpanWasCreated(manager); + + // when + manager.commit(transaction); + // then + thenOneSpanWasReported(manager, span); + } + + @Test + void should_create_a_new_span_for_a_new_rolled_back_transaction() { + // given + TracePlatformTransactionManager manager = manager(); + setupTransactionStatusWithNewTransactionStatusEqualTo(true); + thenThreadLocalIsClear(manager); + + // when + TransactionStatus transaction = manager.getTransaction(null); + // then + SimpleSpan span = thenATaggedSpanWasCreated(manager); + + // when + manager.rollback(transaction); + // then + thenOneSpanWasReported(manager, span); + } + + @Test + void should_continue_a_span_for_a_the_same_transaction() { + // given + TracePlatformTransactionManager manager = managerWithManualFallback(); + setupTransactionStatusWithNewTransactionStatusEqualTo(false); + SimpleSpan firstSpan = threadLocalSpan(manager); + + // when + TransactionStatus transaction = manager.getTransaction(null); + // then + SpanAndScope spanAndScope = manager.threadLocalSpan.get(); + then(spanAndScope).isNotNull(); + then(spanAndScope.getSpan()).isSameAs(firstSpan); + + // when + manager.commit(transaction); + // then + thenPreviouslyCreatedSpanWasFinished(firstSpan); + then(firstSpan).isSameAs(manager.threadLocalSpan.get().getSpan()); + manager.threadLocalSpan.remove(); + thenThreadLocalIsClear(manager); + } + + private SimpleSpan threadLocalSpan(TracePlatformTransactionManager manager) { + SimpleSpan firstSpan = tracer.nextSpan().start(); + manager.threadLocalSpan.set(firstSpan); + return firstSpan; + } + + @Test + void should_report_a_fallback_span_when_exception_occurred_while_getting_transaction() { + TracePlatformTransactionManager manager = manager(); + BDDMockito.given(this.delegate.getTransaction(BDDMockito.any())) + .willThrow(new TransactionTimedOutException("boom")); + thenThreadLocalIsClear(manager); + + BDDAssertions.thenThrownBy(() -> manager.getTransaction(null)).isInstanceOf(TransactionTimedOutException.class); + + SimpleSpan span = tracer.getOnlySpan(); + then(span.throwable).isInstanceOf(TransactionTimedOutException.class); + } + + @Test + void should_report_a_span_when_exception_occurred_while_committing_transaction() { + TracePlatformTransactionManager manager = manager(); + BDDMockito.willThrow(new TransactionTimedOutException("boom")).given(this.delegate).commit(BDDMockito.any()); + threadLocalSpan(manager); + + BDDAssertions.thenThrownBy(() -> manager.commit(null)).isInstanceOf(TransactionTimedOutException.class); + + SimpleSpan span = tracer.getOnlySpan(); + then(span.throwable).isInstanceOf(TransactionTimedOutException.class); + manager.threadLocalSpan.remove(); + thenThreadLocalIsClear(manager); + } + + @Test + void should_report_a_span_when_exception_occurred_while_rolling_back_transaction() { + TracePlatformTransactionManager manager = manager(); + BDDMockito.willThrow(new TransactionTimedOutException("boom")).given(this.delegate).rollback(BDDMockito.any()); + threadLocalSpan(manager); + + BDDAssertions.thenThrownBy(() -> manager.rollback(null)).isInstanceOf(TransactionTimedOutException.class); + + SimpleSpan span = tracer.getOnlySpan(); + then(span.throwable).isInstanceOf(TransactionTimedOutException.class); + manager.threadLocalSpan.remove(); + thenThreadLocalIsClear(manager); + } + + private void setupTransactionStatusWithNewTransactionStatusEqualTo(boolean transactionStatus) { + BDDMockito.given(this.delegate.getTransaction(BDDMockito.any())) + .willReturn(new SimpleTransactionStatus(transactionStatus)); + } + + private void thenThreadLocalIsClear(TracePlatformTransactionManager manager) { + then(manager.threadLocalSpan.get()).as("Thread local was cleared").isNull(); + } + + private void thenOneSpanWasReported(TracePlatformTransactionManager manager, SimpleSpan firstSpan) { + thenPreviouslyCreatedSpanWasFinished(firstSpan); + thenThreadLocalIsClear(manager); + } + + private void thenPreviouslyCreatedSpanWasFinished(SimpleSpan firstSpan) { + then(firstSpan.ended).as("The previously created span was finished").isTrue(); + then(tracer.getOnlySpan()).as("The previously created span was reported").isSameAs(firstSpan); + } + + private TracePlatformTransactionManager manager() { + final TracePlatformTransactionManager manager = new TracePlatformTransactionManager(this.delegate, + beanFactory()); + manager.initialize(); + return manager; + } + + private TracePlatformTransactionManager managerWithManualFallback() { + final TracePlatformTransactionManager manager = new TracePlatformTransactionManager(this.delegate, + beanFactory()) { + @Override + Span fallbackSpan() { + return new SimpleSpan().start(); + } + }; + manager.initialize(); + return manager; + } + + private SimpleSpan thenATaggedSpanWasCreated(TracePlatformTransactionManager manager) { + SpanAndScope spanAndScope = manager.threadLocalSpan.get(); + then(spanAndScope).isNotNull(); + SimpleSpan span = (SimpleSpan) spanAndScope.getSpan(); + then(span.started).isTrue(); + then(span.tags).isNotEmpty(); + return span; + } + + private BeanFactory beanFactory() { + StaticListableBeanFactory beanFactory = new StaticListableBeanFactory(); + beanFactory.addBean("tracer", tracer); + return beanFactory; + } + +} diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/NoOpCurrentTraceContext.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpCurrentTraceContext.java similarity index 92% rename from spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/NoOpCurrentTraceContext.java rename to spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpCurrentTraceContext.java index 394c3eefb..2634c18cf 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/NoOpCurrentTraceContext.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpCurrentTraceContext.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.deployer; +package org.springframework.cloud.sleuth.tracer; import java.util.concurrent.Callable; import java.util.concurrent.Executor; @@ -29,7 +29,7 @@ import org.springframework.cloud.sleuth.TraceContext; * @author Marcin Grzejszczak * @since 3.0.0 */ -class NoOpCurrentTraceContext implements CurrentTraceContext { +public class NoOpCurrentTraceContext implements CurrentTraceContext { @Override public TraceContext context() { diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/NoOpSpanInScope.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpSpanInScope.java similarity index 87% rename from spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/NoOpSpanInScope.java rename to spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpSpanInScope.java index d2339918a..ac13149a4 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/NoOpSpanInScope.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpSpanInScope.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.deployer; +package org.springframework.cloud.sleuth.tracer; import org.springframework.cloud.sleuth.Tracer; @@ -24,7 +24,7 @@ import org.springframework.cloud.sleuth.Tracer; * @author Marcin Grzejszczak * @since 3.0.0 */ -class NoOpSpanInScope implements Tracer.SpanInScope { +public class NoOpSpanInScope implements Tracer.SpanInScope { @Override public void close() { diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/NoOpTraceContext.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpTraceContext.java similarity index 90% rename from spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/NoOpTraceContext.java rename to spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpTraceContext.java index 057b4b41f..c8fa01ac6 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/NoOpTraceContext.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/NoOpTraceContext.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.deployer; +package org.springframework.cloud.sleuth.tracer; import org.springframework.cloud.sleuth.TraceContext; @@ -24,7 +24,7 @@ import org.springframework.cloud.sleuth.TraceContext; * @author Marcin Grzejszczak * @since 3.0.0 */ -class NoOpTraceContext implements TraceContext { +public class NoOpTraceContext implements TraceContext { @Override public String traceId() { diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/SimpleSpan.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleSpan.java similarity index 75% rename from spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/SimpleSpan.java rename to spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleSpan.java index 86cdfd169..ad86a5817 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/SimpleSpan.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleSpan.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.deployer; +package org.springframework.cloud.sleuth.tracer; import java.util.HashMap; import java.util.Map; @@ -28,13 +28,15 @@ import org.springframework.cloud.sleuth.TraceContext; * @author Marcin Grzejszczak * @since 3.0.0 */ -class SimpleSpan implements Span { +public class SimpleSpan implements Span { - Map tags = new HashMap<>(); + public Map tags = new HashMap<>(); - boolean started; + public boolean started; - boolean ended; + public boolean ended; + + public Throwable throwable; @Override public boolean isNoop() { @@ -47,29 +49,30 @@ class SimpleSpan implements Span { } @Override - public Span start() { + public SimpleSpan start() { this.started = true; return this; } @Override - public Span name(String name) { + public SimpleSpan name(String name) { return this; } @Override - public Span event(String value) { + public SimpleSpan event(String value) { return this; } @Override - public Span tag(String key, String value) { + public SimpleSpan tag(String key, String value) { this.tags.put(key, value); return this; } @Override - public Span error(Throwable throwable) { + public SimpleSpan error(Throwable throwable) { + this.throwable = throwable; return this; } diff --git a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/SimpleTracer.java b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleTracer.java similarity index 92% rename from spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/SimpleTracer.java rename to spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleTracer.java index 0b56fc984..c4cbf633e 100644 --- a/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/instrument/deployer/SimpleTracer.java +++ b/spring-cloud-sleuth-instrumentation/src/test/java/org/springframework/cloud/sleuth/tracer/SimpleTracer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.cloud.sleuth.instrument.deployer; +package org.springframework.cloud.sleuth.tracer; import java.util.ArrayList; import java.util.HashMap; @@ -36,16 +36,16 @@ import org.springframework.cloud.sleuth.Tracer; * @author Marcin Grzejszczak * @since 3.0.0 */ -class SimpleTracer implements Tracer { +public class SimpleTracer implements Tracer { - List spans = new ArrayList<>(); + public List spans = new ArrayList<>(); @Override public Span nextSpan(Span parent) { return new SimpleSpan(); } - SimpleSpan getOnlySpan() { + public SimpleSpan getOnlySpan() { BDDAssertions.then(this.spans).hasSize(1); SimpleSpan span = this.spans.get(0); BDDAssertions.then(span.started).as("Span must be started").isTrue(); @@ -69,7 +69,7 @@ class SimpleTracer implements Tracer { } @Override - public Span nextSpan() { + public SimpleSpan nextSpan() { final SimpleSpan span = new SimpleSpan(); this.spans.add(span); return span;