diff --git a/.editorconfig b/.editorconfig index ddda9782f..ffe385c6d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,9 @@ root = true +[*] +end_of_line = crlf +insert_final_newline = true + [*.java] indent_style = tab indent_size = 4 diff --git a/pom.xml b/pom.xml index 2fb7cb011..286d8b4e4 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,7 @@ 3.1.3-SNAPSHOT 3.0.3-SNAPSHOT 3.0.3-SNAPSHOT + 2.3.2-SNAPSHOT 5.13.2 0.32.0 2.3.4.RELEASE @@ -228,6 +229,13 @@ pom import + + org.springframework.cloud + spring-cloud-task-dependencies + ${spring-cloud-task.version} + pom + import + org.springframework.security.oauth.boot spring-security-oauth2-autoconfigure diff --git a/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/SpanAndScope.java b/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/SpanAndScope.java new file mode 100644 index 000000000..e18e028d7 --- /dev/null +++ b/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/SpanAndScope.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * Container object for {@link Span} and its corresponding {@link Tracer.SpanInScope}. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class SpanAndScope { + + private final Span span; + + private final Tracer.SpanInScope scope; + + public SpanAndScope(Span span, Tracer.SpanInScope scope) { + this.span = span; + this.scope = scope; + } + + public Span getSpan() { + return this.span; + } + + public Tracer.SpanInScope getScope() { + return this.scope; + } + +} diff --git a/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/ThreadLocalSpan.java b/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/ThreadLocalSpan.java new file mode 100644 index 000000000..aa2c0226b --- /dev/null +++ b/spring-cloud-sleuth-api/src/main/java/org/springframework/cloud/sleuth/ThreadLocalSpan.java @@ -0,0 +1,90 @@ +/* + * 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; + +import java.util.Deque; +import java.util.NoSuchElementException; +import java.util.concurrent.LinkedBlockingDeque; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Represents a {@link Span} stored in thread local. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class ThreadLocalSpan { + + private static final Log log = LogFactory.getLog(ThreadLocalSpan.class); + + private final ThreadLocal threadLocalSpan = new ThreadLocal<>(); + + private final Deque spans = new LinkedBlockingDeque<>(); + + private final Tracer tracer; + + public ThreadLocalSpan(Tracer tracer) { + this.tracer = tracer; + } + + /** + * Sets given span and scope. + * @param span - span to be put in scope + */ + public void set(Span span) { + Tracer.SpanInScope spanInScope = this.tracer.withSpan(span); + SpanAndScope newSpanAndScope = new SpanAndScope(span, spanInScope); + SpanAndScope scope = this.threadLocalSpan.get(); + if (scope != null) { + this.spans.addFirst(scope); + } + this.threadLocalSpan.set(newSpanAndScope); + } + + /** + * @return currently stored span and scope + */ + public SpanAndScope get() { + return this.threadLocalSpan.get(); + } + + /** + * Removes the current span from thread local and brings back the previous span to the + * current thread local. + */ + public void remove() { + this.threadLocalSpan.remove(); + if (this.spans.isEmpty()) { + return; + } + try { + SpanAndScope span = this.spans.removeFirst(); + if (log.isDebugEnabled()) { + log.debug("Took span [" + span + "] from thread local"); + } + this.threadLocalSpan.set(span); + } + catch (NoSuchElementException ex) { + if (log.isTraceEnabled()) { + log.trace("Failed to remove a span from the queue", ex); + } + } + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/pom.xml b/spring-cloud-sleuth-autoconfigure/pom.xml index 15f4a93a0..769757ac6 100644 --- a/spring-cloud-sleuth-autoconfigure/pom.xml +++ b/spring-cloud-sleuth-autoconfigure/pom.xml @@ -103,6 +103,11 @@ spring-cloud-context true + + org.springframework.cloud + spring-cloud-starter-task + true + io.reactivex rxjava diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceApplicationRunnerBeanPostProcessor.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceApplicationRunnerBeanPostProcessor.java new file mode 100644 index 000000000..5efd90f4c --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceApplicationRunnerBeanPostProcessor.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.task; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.ApplicationRunner; +import org.springframework.cloud.sleuth.instrument.task.TraceApplicationRunner; + +/** + * Registers beans related to task scheduling. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TraceApplicationRunnerBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + + public TraceApplicationRunnerBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof ApplicationRunner && !(bean instanceof TraceApplicationRunner)) { + return new TraceApplicationRunner(this.beanFactory, (ApplicationRunner) bean, beanName); + } + return bean; + } + +} diff --git a/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceCommandLineRunnerBeanPostProcessor.java b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceCommandLineRunnerBeanPostProcessor.java new file mode 100644 index 000000000..9fc69d7da --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceCommandLineRunnerBeanPostProcessor.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.task; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.cloud.sleuth.instrument.task.TraceCommandLineRunner; + +/** + * Registers beans related to task scheduling. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TraceCommandLineRunnerBeanPostProcessor implements BeanPostProcessor { + + private final BeanFactory beanFactory; + + public TraceCommandLineRunnerBeanPostProcessor(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof CommandLineRunner && !(bean instanceof TraceCommandLineRunner)) { + return new TraceCommandLineRunner(this.beanFactory, (CommandLineRunner) bean, beanName); + } + return bean; + } + +} 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 new file mode 100644 index 000000000..591ca5941 --- /dev/null +++ b/spring-cloud-sleuth-autoconfigure/src/main/java/org/springframework/cloud/sleuth/autoconfig/instrument/task/TraceTaskAutoConfiguration.java @@ -0,0 +1,61 @@ +/* + * 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.task; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Value; +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.cloud.sleuth.instrument.task.TraceTaskExecutionListener; +import org.springframework.cloud.task.listener.TaskExecutionListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Registers beans related to Spring Cloud Task scheduling. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(TaskExecutionListener.class) +@ConditionalOnProperty(value = "spring.sleuth.task.enabled", matchIfMissing = true) +@ConditionalOnBean(Tracer.class) +@AutoConfigureAfter(BraveAutoConfiguration.class) +public class TraceTaskAutoConfiguration { + + @Bean + TraceTaskExecutionListener traceTaskExecutionListener(Tracer tracer, + @Value("${spring.application.name:default}") String appName) { + return new TraceTaskExecutionListener(tracer, appName); + } + + @Bean + static TraceCommandLineRunnerBeanPostProcessor traceCommandLineRunnerBeanPostProcessor(BeanFactory beanFactory) { + return new TraceCommandLineRunnerBeanPostProcessor(beanFactory); + } + + @Bean + static TraceApplicationRunnerBeanPostProcessor traceApplicationRunnerBeanPostProcessor(BeanFactory beanFactory) { + return new TraceApplicationRunnerBeanPostProcessor(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 d655251ff..e46fecde3 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 @@ -128,7 +128,13 @@ { "name": "spring.sleuth.integration.enabled", "type": "java.lang.Boolean", - "description": "Enable Spring Integration sleuth instrumentation.", + "description": "Enable Spring Integration instrumentation.", + "defaultValue": true + }, + { + "name": "spring.sleuth.task.enabled", + "type": "java.lang.Boolean", + "description": "Enable Spring Cloud Task instrumentation.", "defaultValue": true } ] 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 1c5ba3eba..5e902cd8a 100644 --- a/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories @@ -6,6 +6,7 @@ org.springframework.cloud.sleuth.autoconfig.instrument.async.TraceAsyncDefaultAu org.springframework.cloud.sleuth.autoconfig.instrument.circuitbreaker.TraceCircuitBreakerAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.instrument.rxjava.TraceRxJavaAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.instrument.quartz.TraceQuartzAutoConfiguration,\ +org.springframework.cloud.sleuth.autoconfig.instrument.task.TraceTaskAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.instrument.web.TraceWebAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.instrument.web.client.TraceWebClientAutoConfiguration,\ org.springframework.cloud.sleuth.autoconfig.instrument.web.client.feign.TraceFeignClientAutoConfiguration,\ diff --git a/spring-cloud-sleuth-instrumentation/pom.xml b/spring-cloud-sleuth-instrumentation/pom.xml index f049e8a35..6aff38d57 100644 --- a/spring-cloud-sleuth-instrumentation/pom.xml +++ b/spring-cloud-sleuth-instrumentation/pom.xml @@ -127,6 +127,11 @@ spring-cloud-starter-gateway true + + org.springframework.cloud + spring-cloud-starter-task + true + org.aspectj aspectjrt diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptor.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptor.java index 9640fdb2a..ed9d3780d 100644 --- a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptor.java +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/messaging/TracingChannelInterceptor.java @@ -18,8 +18,6 @@ package org.springframework.cloud.sleuth.instrument.messaging; import java.util.Iterator; import java.util.Map; -import java.util.NoSuchElementException; -import java.util.concurrent.LinkedBlockingDeque; import java.util.function.Function; import org.apache.commons.logging.Log; @@ -28,6 +26,8 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; 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.cloud.sleuth.propagation.Propagator; import org.springframework.cloud.stream.binder.BinderType; @@ -107,7 +107,7 @@ public final class TracingChannelInterceptor extends ChannelInterceptorAdapter private final Propagator propagator; - private final ThreadLocalSpan threadLocalSpan = new ThreadLocalSpan(); + private final ThreadLocalSpan threadLocalSpan; private final Function remoteServiceNameMapper; @@ -115,6 +115,7 @@ public final class TracingChannelInterceptor extends ChannelInterceptorAdapter Propagator.Setter setter, Propagator.Getter getter, Function remoteServiceNameMapper, MessageSpanCustomizer messageSpanCustomizer) { this.tracer = tracer; + this.threadLocalSpan = new ThreadLocalSpan(tracer); this.propagator = propagator; this.injector = setter; this.extractor = getter; @@ -163,8 +164,7 @@ public final class TracingChannelInterceptor extends ChannelInterceptorAdapter } private void setSpanInScope(Span span) { - Tracer.SpanInScope spanInScope = this.tracer.withSpan(span); - this.threadLocalSpan.set(new SpanAndScope(span, spanInScope)); + this.threadLocalSpan.set(span); if (log.isDebugEnabled()) { log.debug("Put span in scope " + span); } @@ -362,8 +362,8 @@ public final class TracingChannelInterceptor extends ChannelInterceptorAdapter if (spanAndScope == null) { return; } - Span span = spanAndScope.span; - Tracer.SpanInScope scope = spanAndScope.scope; + Span span = spanAndScope.getSpan(); + Tracer.SpanInScope scope = spanAndScope.getScope(); if (span.isNoop()) { if (log.isDebugEnabled()) { log.debug("Span " + span + " is noop - will stope the scope"); @@ -424,57 +424,3 @@ public final class TracingChannelInterceptor extends ChannelInterceptorAdapter } } - -class SpanAndScope { - - final Span span; - - final Tracer.SpanInScope scope; - - SpanAndScope(Span span, Tracer.SpanInScope scope) { - this.span = span; - this.scope = scope; - } - -} - -class ThreadLocalSpan { - - private static final Log log = LogFactory.getLog(ThreadLocalSpan.class); - - final ThreadLocal threadLocalSpan = new ThreadLocal<>(); - - final LinkedBlockingDeque spans = new LinkedBlockingDeque<>(); - - void set(SpanAndScope spanAndScope) { - SpanAndScope scope = this.threadLocalSpan.get(); - if (scope != null) { - this.spans.addFirst(scope); - } - this.threadLocalSpan.set(spanAndScope); - } - - SpanAndScope get() { - return this.threadLocalSpan.get(); - } - - void remove() { - this.threadLocalSpan.remove(); - if (this.spans.isEmpty()) { - return; - } - try { - SpanAndScope span = this.spans.removeFirst(); - if (log.isDebugEnabled()) { - log.debug("Took span [" + span + "] from thread local"); - } - this.threadLocalSpan.set(span); - } - catch (NoSuchElementException ex) { - if (log.isTraceEnabled()) { - log.trace("Failed to remove a span from the queue", ex); - } - } - } - -} diff --git a/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/task/TraceApplicationRunner.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/task/TraceApplicationRunner.java new file mode 100644 index 000000000..4e3f6c77d --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/task/TraceApplicationRunner.java @@ -0,0 +1,65 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.task; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; + +/** + * Trace representation of a {@link ApplicationRunner}. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TraceApplicationRunner implements ApplicationRunner { + + private final BeanFactory beanFactory; + + private final ApplicationRunner delegate; + + private final String beanName; + + private Tracer tracer; + + public TraceApplicationRunner(BeanFactory beanFactory, ApplicationRunner delegate, String beanName) { + this.beanFactory = beanFactory; + this.delegate = delegate; + this.beanName = beanName; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + Span span = tracer().nextSpan().name(this.beanName); + try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) { + this.delegate.run(args); + } + finally { + span.end(); + } + } + + 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/task/TraceCommandLineRunner.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/task/TraceCommandLineRunner.java new file mode 100644 index 000000000..70b9ee320 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/task/TraceCommandLineRunner.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.task; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.cloud.sleuth.Span; +import org.springframework.cloud.sleuth.Tracer; + +/** + * Trace representation of a {@link CommandLineRunner}. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TraceCommandLineRunner implements CommandLineRunner { + + private final BeanFactory beanFactory; + + private final CommandLineRunner delegate; + + private final String beanName; + + private Tracer tracer; + + public TraceCommandLineRunner(BeanFactory beanFactory, CommandLineRunner delegate, String beanName) { + this.beanFactory = beanFactory; + this.delegate = delegate; + this.beanName = beanName; + } + + @Override + public void run(String... args) throws Exception { + Span span = tracer().nextSpan().name(this.beanName); + try (Tracer.SpanInScope spanInScope = tracer().withSpan(span.start())) { + this.delegate.run(args); + } + finally { + span.end(); + } + } + + 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/task/TraceTaskExecutionListener.java b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/task/TraceTaskExecutionListener.java new file mode 100644 index 000000000..d50d7ab00 --- /dev/null +++ b/spring-cloud-sleuth-instrumentation/src/main/java/org/springframework/cloud/sleuth/instrument/task/TraceTaskExecutionListener.java @@ -0,0 +1,89 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.task; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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.cloud.task.listener.TaskExecutionListener; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.core.Ordered; + +/** + * Sets the span upon starting and closes it upon ending a task. + * + * @author Marcin Grzejszczak + * @since 3.1.0 + */ +public class TraceTaskExecutionListener implements TaskExecutionListener, Ordered { + + private static final Log log = LogFactory.getLog(TraceTaskExecutionListener.class); + + private final Tracer tracer; + + private final ThreadLocalSpan threadLocalSpan; + + private final String projectName; + + public TraceTaskExecutionListener(Tracer tracer, String projectName) { + this.tracer = tracer; + this.threadLocalSpan = new ThreadLocalSpan(tracer); + this.projectName = projectName; + } + + @Override + public void onTaskStartup(TaskExecution taskExecution) { + Span span = this.tracer.nextSpan().name(this.projectName).start(); + this.threadLocalSpan.set(span); + if (log.isDebugEnabled()) { + log.debug("Put the span [" + span + "] to thread local"); + } + } + + @Override + public void onTaskEnd(TaskExecution taskExecution) { + SpanAndScope spanAndScope = this.threadLocalSpan.get(); + Span span = spanAndScope.getSpan(); + span.end(); + spanAndScope.getScope().close(); + if (log.isDebugEnabled()) { + log.debug("Removed the [" + span + "] from thread local"); + } + } + + @Override + public void onTaskFailed(TaskExecution taskExecution, Throwable throwable) { + SpanAndScope spanAndScope = this.threadLocalSpan.get(); + Span span = spanAndScope.getSpan(); + span.error(throwable); + span.end(); + spanAndScope.getScope().close(); + if (log.isDebugEnabled()) { + log.debug("Removed the [" + span + "] from thread local and added error"); + } + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + +} diff --git a/tests/brave/pom.xml b/tests/brave/pom.xml index 09478bd12..8796d03bf 100644 --- a/tests/brave/pom.xml +++ b/tests/brave/pom.xml @@ -50,6 +50,7 @@ spring-cloud-sleuth-instrumentation-reactor-tests spring-cloud-sleuth-instrumentation-rxjava-tests spring-cloud-sleuth-instrumentation-scheduling-tests + spring-cloud-sleuth-instrumentation-task-tests spring-cloud-sleuth-instrumentation-webflux-tests spring-cloud-sleuth-zipkin-tests diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-scheduling-tests/pom.xml b/tests/brave/spring-cloud-sleuth-instrumentation-scheduling-tests/pom.xml index da9a7a566..45d3de776 100644 --- a/tests/brave/spring-cloud-sleuth-instrumentation-scheduling-tests/pom.xml +++ b/tests/brave/spring-cloud-sleuth-instrumentation-scheduling-tests/pom.xml @@ -51,10 +51,6 @@ - - org.springframework.cloud - spring-cloud-starter-sleuth - org.springframework.cloud spring-cloud-starter-sleuth diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-task-tests/pom.xml b/tests/brave/spring-cloud-sleuth-instrumentation-task-tests/pom.xml new file mode 100644 index 000000000..667d54797 --- /dev/null +++ b/tests/brave/spring-cloud-sleuth-instrumentation-task-tests/pom.xml @@ -0,0 +1,81 @@ + + + + + 4.0.0 + + spring-cloud-sleuth-instrumentation-task-tests + jar + Spring Cloud Sleuth Brave Task Instrumentation Tests + Spring Cloud Sleuth Brave Task Instrumentation Tests + + + org.springframework.cloud + spring-cloud-sleuth-tests-brave + 3.1.0-SNAPSHOT + .. + + + + true + + + + + + + maven-deploy-plugin + + true + + + + + + + + org.springframework.cloud + spring-cloud-sleuth-tests-common + ${project.version} + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.cloud + spring-cloud-starter-task + + + org.springframework.boot + spring-boot-starter-test + + + io.zipkin.brave + brave-tests + + + org.awaitility + awaitility + + + + diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-task-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/task/SpringCloudTaskIntegrationTests.java b/tests/brave/spring-cloud-sleuth-instrumentation-task-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/task/SpringCloudTaskIntegrationTests.java new file mode 100644 index 000000000..55d08c60a --- /dev/null +++ b/tests/brave/spring-cloud-sleuth-instrumentation-task-tests/src/test/java/org/springframework/cloud/sleuth/brave/instrument/task/SpringCloudTaskIntegrationTests.java @@ -0,0 +1,53 @@ +/* + * 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.brave.instrument.task; + +import brave.sampler.Sampler; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.sleuth.brave.BraveTestSpanHandler; +import org.springframework.cloud.sleuth.test.TestSpanHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; + +@SpringBootTest +@ContextConfiguration(classes = SpringCloudTaskIntegrationTests.Config.class) +public class SpringCloudTaskIntegrationTests + extends org.springframework.cloud.sleuth.instrument.task.SpringCloudTaskIntegrationTests { + + @Configuration(proxyBeanMethods = false) + static class Config { + + @Bean + TestSpanHandler testSpanHandlerSupplier(brave.test.TestSpanHandler testSpanHandler) { + return new BraveTestSpanHandler(testSpanHandler); + } + + @Bean + Sampler alwaysSampler() { + return Sampler.ALWAYS_SAMPLE; + } + + @Bean + brave.test.TestSpanHandler braveTestSpanHandler() { + return new brave.test.TestSpanHandler(); + } + + } + +} diff --git a/tests/brave/spring-cloud-sleuth-instrumentation-task-tests/src/test/resources/application.yml b/tests/brave/spring-cloud-sleuth-instrumentation-task-tests/src/test/resources/application.yml new file mode 100644 index 000000000..2b87cbf49 --- /dev/null +++ b/tests/brave/spring-cloud-sleuth-instrumentation-task-tests/src/test/resources/application.yml @@ -0,0 +1 @@ +logging.level.org.springframework.cloud: DEBUG diff --git a/tests/common/pom.xml b/tests/common/pom.xml index 628288865..01c5cdc48 100644 --- a/tests/common/pom.xml +++ b/tests/common/pom.xml @@ -84,6 +84,11 @@ spring-cloud-starter-gateway true + + org.springframework.cloud + spring-cloud-starter-task + true + org.springframework.cloud spring-cloud-starter-circuitbreaker-resilience4j diff --git a/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/task/SpringCloudTaskIntegrationTests.java b/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/task/SpringCloudTaskIntegrationTests.java new file mode 100644 index 000000000..e1c5f83f3 --- /dev/null +++ b/tests/common/src/main/java/org/springframework/cloud/sleuth/instrument/task/SpringCloudTaskIntegrationTests.java @@ -0,0 +1,107 @@ +/* + * 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.task; + +import java.util.Iterator; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.cloud.sleuth.exporter.FinishedSpan; +import org.springframework.cloud.sleuth.test.TestSpanHandler; +import org.springframework.cloud.task.configuration.EnableTask; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.BDDAssertions.then; + +@ContextConfiguration(classes = SpringCloudTaskIntegrationTests.TestConfig.class) +@TestPropertySource(properties = "spring.application.name=MyApplication") +public abstract class SpringCloudTaskIntegrationTests { + + @Autowired + TestSpanHandler spans; + + @Test + public void should_pass_tracing_information_when_using_spring_cloud_task() { + Set traceIds = this.spans.reportedSpans().stream().map(FinishedSpan::getTraceId) + .collect(Collectors.toSet()); + then(traceIds).as("There's one traceid").hasSize(1); + Set spanIds = this.spans.reportedSpans().stream().map(FinishedSpan::getSpanId) + .collect(Collectors.toSet()); + + then(spanIds).as("There are 3 spans").hasSize(3); + Iterator spanIterator = this.spans.reportedSpans().iterator(); + + FinishedSpan first = spanIterator.next(); + FinishedSpan second = spanIterator.next(); + FinishedSpan third = spanIterator.next(); + then(first.getName()).isEqualTo("myApplicationRunner"); + then(second.getName()).isEqualTo("myCommandLineRunner"); + then(third.getName()).isEqualTo("MyApplication"); + } + + @Configuration(proxyBeanMethods = false) + @EnableAutoConfiguration + @EnableTask + public static class TestConfig { + + @Bean + MyCommandLineRunner myCommandLineRunner() { + return new MyCommandLineRunner(); + } + + @Bean + MyApplicationRunner myApplicationRunner() { + return new MyApplicationRunner(); + } + + } + + static class MyCommandLineRunner implements CommandLineRunner { + + private static final Log log = LogFactory.getLog(MyCommandLineRunner.class); + + @Override + public void run(String... args) throws Exception { + log.info("Ran MyCommandLineRunner"); + } + + } + + static class MyApplicationRunner implements ApplicationRunner { + + private static final Log log = LogFactory.getLog(MyApplicationRunner.class); + + @Override + public void run(ApplicationArguments args) throws Exception { + log.info("Ran MyApplicationRunner"); + } + + } + +}