Added TX support
fixes gh-1941
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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,\
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -129,13 +129,14 @@ public class TraceAppDeployer implements AppDeployer {
|
||||
@Override
|
||||
public Mono<AppStatus> 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<AppStatus> 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
|
||||
|
||||
@@ -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 <T> Mono<T> tracedMono(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
|
||||
@NonNull String childSpanName, @NonNull Supplier<Mono<T>> supplier,
|
||||
@NonNull Consumer<Span> spanCustomizer) {
|
||||
@NonNull BiConsumer<T, Span> spanCustomizer) {
|
||||
return runMonoSupplierInScope(supplier, spanCustomizer).contextWrite(
|
||||
context -> ReactorSleuth.enhanceContext(tracer, currentTraceContext, context, childSpanName));
|
||||
}
|
||||
|
||||
private static <T> Mono<T> runMonoSupplierInScope(Supplier<Mono<T>> supplier, Consumer<Span> spanCustomizer) {
|
||||
private static <T> Mono<T> runMonoSupplierInScope(Supplier<Mono<T>> supplier, BiConsumer<T, Span> 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 <T> Mono<T> tracedMono(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
|
||||
@NonNull String childSpanName, @NonNull Supplier<Mono<T>> 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 <T> Mono<T> tracedMono(@NonNull Tracer tracer, @NonNull Span span,
|
||||
@NonNull Supplier<Mono<T>> 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 <T> Flux<T> tracedFlux(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
|
||||
@NonNull String childSpanName, @NonNull Supplier<Flux<T>> supplier,
|
||||
@NonNull Consumer<Span> spanCustomizer) {
|
||||
@NonNull BiConsumer<T, Span> spanCustomizer) {
|
||||
return runFluxSupplierInScope(supplier, spanCustomizer).contextWrite(
|
||||
context -> ReactorSleuth.enhanceContext(tracer, currentTraceContext, context, childSpanName));
|
||||
}
|
||||
@@ -415,17 +418,20 @@ public abstract class ReactorSleuth {
|
||||
*/
|
||||
public static <T> Flux<T> tracedFlux(@NonNull Tracer tracer, @NonNull Span span,
|
||||
@NonNull Supplier<Flux<T>> supplier) {
|
||||
return runFluxSupplierInScope(supplier, span1 -> {
|
||||
return runFluxSupplierInScope(supplier, (o, span1) -> {
|
||||
}).contextWrite(context -> ReactorSleuth.putSpanInScope(tracer, context, span));
|
||||
}
|
||||
|
||||
private static <T> Flux<T> runFluxSupplierInScope(Supplier<Flux<T>> supplier, Consumer<Span> spanCustomizer) {
|
||||
private static <T> Flux<T> runFluxSupplierInScope(Supplier<Flux<T>> supplier, BiConsumer<T, Span> 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 <T> Flux<T> tracedFlux(@NonNull Tracer tracer, @NonNull CurrentTraceContext currentTraceContext,
|
||||
@NonNull String childSpanName, @NonNull Supplier<Flux<T>> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ReactiveTransaction> 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<Void> 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<Void> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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..",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
@@ -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() {
|
||||
@@ -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<String, String> tags = new HashMap<>();
|
||||
public Map<String, String> 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;
|
||||
}
|
||||
|
||||
@@ -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<SimpleSpan> spans = new ArrayList<>();
|
||||
public List<SimpleSpan> 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;
|
||||
Reference in New Issue
Block a user