Added batch instrumentation

fixes gh-1904
This commit is contained in:
Marcin Grzejszczak
2021-04-30 14:12:16 +02:00
parent 302e718fff
commit fc0a719d12
20 changed files with 855 additions and 166 deletions

View File

@@ -584,11 +584,18 @@ This feature is available for all tracer implementations.
If you have Spring Cloud Deployer running on the classpath, we wrap the `AppDeployer` in a trace representation. We are polling the application for its status at a default interval. You can change that default by setting the `spring.sleuth.deployer.status-poll-delay` property.
In order to disable this instrumentation set `spring.sleuth.deployer.enabled` to `false`.
[[sleuth-deployer-integration]]
[[sleuth-rsocket-integration]]
== Spring RSocket
This feature is available for all tracer implementations.
If you have Spring RSocket running on the classpath, we wrap the inbound and outbound communication to propagate the tracing context via the metadata.
In order to disable this instrumentation set `spring.sleuth.rsocket.enabled` to `false`.
[[sleuth-batch-integration]]
== Spring Batch
This feature is available for all tracer implementations.
If you have Spring RSocket running on the classpath, we wrap the inbound and outbound communication to propagate the tracing context via the metadata.
In order to disable this instrumentation set `spring.sleuth.batch.enabled` to `false`.

View File

@@ -93,6 +93,11 @@
<artifactId>spring-boot-starter-websocket</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
@@ -433,6 +438,11 @@
<artifactId>archunit-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.autoconfig.instrument.batch;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
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 Batch.
*
* @author Marcin Grzejszczak
* @since 3.1.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(JobBuilderFactory.class)
@ConditionalOnBean(Tracer.class)
@ConditionalOnProperty(value = "spring.sleuth.batch.enabled", matchIfMissing = true)
@AutoConfigureAfter(BraveAutoConfiguration.class)
@AutoConfigureBefore(BatchAutoConfiguration.class)
public class TraceBatchAutoConfiguration {
@Bean
static TraceJobBuilderFactoryBeanPostProcessor traceJobBuilderFactoryBeanPostProcessor(BeanFactory beanFactory) {
return new TraceJobBuilderFactoryBeanPostProcessor(beanFactory);
}
@Bean
static TraceStepBuilderFactoryBeanPostProcessor traceStepBuilderFactoryBeanPostProcessor(BeanFactory beanFactory) {
return new TraceStepBuilderFactoryBeanPostProcessor(beanFactory);
}
}

View File

@@ -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.batch;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.sleuth.instrument.batch.TraceJobBuilderFactory;
/**
* Bean post processor for {@link JobBuilderFactory}.
*
* @author Marcin Grzejszczak
* @since 3.1.0
*/
public class TraceJobBuilderFactoryBeanPostProcessor implements BeanPostProcessor {
private final BeanFactory beanFactory;
public TraceJobBuilderFactoryBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof JobBuilderFactory && !(bean instanceof TraceJobBuilderFactory)) {
return new TraceJobBuilderFactory(this.beanFactory, (JobBuilderFactory) bean);
}
return bean;
}
}

View File

@@ -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.batch;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cloud.sleuth.instrument.batch.TraceStepBuilderFactory;
/**
* Bean post processor for {@link StepBuilderFactory}.
*
* @author Marcin Grzejszczak
* @since 3.1.0
*/
public class TraceStepBuilderFactoryBeanPostProcessor implements BeanPostProcessor {
private final BeanFactory beanFactory;
public TraceStepBuilderFactoryBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof StepBuilderFactory && !(bean instanceof TraceStepBuilderFactory)) {
return new TraceStepBuilderFactory(this.beanFactory, (StepBuilderFactory) bean);
}
return bean;
}
}

View File

@@ -1,88 +1,88 @@
/*
* 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.rsocket;
import java.util.List;
import io.rsocket.RSocket;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration;
import org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.rsocket.TracingRSocketConnectorConfigurer;
import org.springframework.cloud.sleuth.instrument.rsocket.TracingRSocketServerCustomizer;
import org.springframework.cloud.sleuth.propagation.Propagator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.messaging.rsocket.RSocketConnectorConfigurer;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketRequester.Builder;
import org.springframework.messaging.rsocket.RSocketStrategies;
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(Tracer.class)
@ConditionalOnProperty(value = "spring.sleuth.rsocket.enabled", matchIfMissing = true)
@ConditionalOnClass({ RSocket.class, RSocketStrategies.class })
@AutoConfigureAfter(BraveAutoConfiguration.class)
@AutoConfigureBefore({ RSocketRequesterAutoConfiguration.class, RSocketServerAutoConfiguration.class })
@EnableConfigurationProperties(SleuthRSocketProperties.class)
public class TraceRSocketAutoConfiguration {
// We're using text instead of objects cause we can have same properties from Brave /
// OTel
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
Builder rSocketRequesterBuilder(RSocketStrategies strategies,
ObjectProvider<RSocketConnectorConfigurer> connectorConfigurerProvider) {
// TODO: should be in spring boot
final Builder builder = RSocketRequester.builder().rsocketStrategies(strategies);
connectorConfigurerProvider.forEach(builder::rsocketConnector);
return builder;
}
private boolean containsZipkinPropagationType(List<String> types) {
return types.stream().anyMatch(s -> s.equalsIgnoreCase("b3"));
}
@Bean
RSocketConnectorConfigurer tracingRSocketConnectorConfigurer(Propagator propagator, Tracer tracer,
@Value("${spring.sleuth.propagation.type:B3}") List<String> types) {
return new TracingRSocketConnectorConfigurer(propagator, tracer, containsZipkinPropagationType(types));
}
// We're using text instead of objects cause we can have same properties from Brave /
// OTel
@Bean
RSocketServerCustomizer tracingRSocketServerCustomizer(Propagator propagator, Tracer tracer,
@Value("${spring.sleuth.propagation.type:B3}") List<String> types) {
return new TracingRSocketServerCustomizer(propagator, tracer, containsZipkinPropagationType(types));
}
}
/*
* 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.rsocket;
import java.util.List;
import io.rsocket.RSocket;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration;
import org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.rsocket.server.RSocketServerCustomizer;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.rsocket.TracingRSocketConnectorConfigurer;
import org.springframework.cloud.sleuth.instrument.rsocket.TracingRSocketServerCustomizer;
import org.springframework.cloud.sleuth.propagation.Propagator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.messaging.rsocket.RSocketConnectorConfigurer;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketRequester.Builder;
import org.springframework.messaging.rsocket.RSocketStrategies;
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(Tracer.class)
@ConditionalOnProperty(value = "spring.sleuth.rsocket.enabled", matchIfMissing = true)
@ConditionalOnClass({ RSocket.class, RSocketStrategies.class })
@AutoConfigureAfter(BraveAutoConfiguration.class)
@AutoConfigureBefore({ RSocketRequesterAutoConfiguration.class, RSocketServerAutoConfiguration.class })
@EnableConfigurationProperties(SleuthRSocketProperties.class)
public class TraceRSocketAutoConfiguration {
// We're using text instead of objects cause we can have same properties from Brave /
// OTel
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
Builder rSocketRequesterBuilder(RSocketStrategies strategies,
ObjectProvider<RSocketConnectorConfigurer> connectorConfigurerProvider) {
// TODO: should be in spring boot
final Builder builder = RSocketRequester.builder().rsocketStrategies(strategies);
connectorConfigurerProvider.forEach(builder::rsocketConnector);
return builder;
}
private boolean containsZipkinPropagationType(List<String> types) {
return types.stream().anyMatch(s -> s.equalsIgnoreCase("b3"));
}
@Bean
RSocketConnectorConfigurer tracingRSocketConnectorConfigurer(Propagator propagator, Tracer tracer,
@Value("${spring.sleuth.propagation.type:B3}") List<String> types) {
return new TracingRSocketConnectorConfigurer(propagator, tracer, containsZipkinPropagationType(types));
}
// We're using text instead of objects cause we can have same properties from Brave /
// OTel
@Bean
RSocketServerCustomizer tracingRSocketServerCustomizer(Propagator propagator, Tracer tracer,
@Value("${spring.sleuth.propagation.type:B3}") List<String> types) {
return new TracingRSocketServerCustomizer(propagator, tracer, containsZipkinPropagationType(types));
}
}

View File

@@ -154,6 +154,12 @@
"type": "java.lang.Boolean",
"description": "Enable Spring Cloud Config Server instrumentation.",
"defaultValue": true
},
{
"name": "spring.sleuth.batch.enabled",
"type": "java.lang.Boolean",
"description": "Enable Spring Batch instrumentation.",
"defaultValue": true
}
]
}

View File

@@ -3,6 +3,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.sleuth.autoconfig.instrument.async.TraceAsyncAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.async.TraceAsyncCustomAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.async.TraceAsyncDefaultAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.batch.TraceBatchAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.config.TraceSpringCloudConfigAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.circuitbreaker.TraceCircuitBreakerAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.deployer.TraceDeployerAutoConfiguration,\

View File

@@ -107,6 +107,11 @@
<artifactId>spring-boot-starter-websocket</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>

View File

@@ -0,0 +1,56 @@
/*
* 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.batch;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.Tracer;
/**
* StepBuilderFactory adding {@link TraceJobExecutionListener}.
*
* @author Marcin Grzejszczak
* @since 3.1.0
*/
public class TraceJobBuilderFactory extends JobBuilderFactory {
private final BeanFactory beanFactory;
private final JobBuilderFactory delegate;
private Tracer tracer;
public TraceJobBuilderFactory(BeanFactory beanFactory, JobBuilderFactory delegate) {
super(null);
this.beanFactory = beanFactory;
this.delegate = delegate;
}
@Override
public JobBuilder get(String name) {
return this.delegate.get(name).listener(new TraceJobExecutionListener(tracer()));
}
private Tracer tracer() {
if (this.tracer == null) {
this.tracer = this.beanFactory.getBean(Tracer.class);
}
return this.tracer;
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.batch;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanAndScope;
import org.springframework.cloud.sleuth.Tracer;
class TraceJobExecutionListener implements JobExecutionListener {
private final Tracer tracer;
private static Map<JobExecution, SpanAndScope> SPANS = new ConcurrentHashMap<>();
TraceJobExecutionListener(Tracer tracer) {
this.tracer = tracer;
}
@Override
public void beforeJob(JobExecution jobExecution) {
Span span = this.tracer.nextSpan().name(jobExecution.getJobInstance().getJobName());
Tracer.SpanInScope spanInScope = this.tracer.withSpan(span.start());
SPANS.put(jobExecution, new SpanAndScope(span, spanInScope));
}
@Override
public void afterJob(JobExecution jobExecution) {
SpanAndScope spanAndScope = SPANS.remove(jobExecution);
List<Throwable> throwables = jobExecution.getFailureExceptions();
Span span = spanAndScope.getSpan();
span.tag("batch.job.name", jobExecution.getJobInstance().getJobName());
span.tag("batch.job.instanceId", String.valueOf(jobExecution.getJobInstance().getInstanceId()));
span.tag("batch.job.executionId", String.valueOf(jobExecution.getId()));
Tracer.SpanInScope scope = spanAndScope.getScope();
if (!throwables.isEmpty()) {
span.error(mergedThrowables(throwables));
}
span.end();
scope.close();
}
private IllegalStateException mergedThrowables(List<Throwable> throwables) {
return new IllegalStateException(
throwables.stream().map(Throwable::toString).collect(Collectors.joining("\n")));
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.batch;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.Tracer;
/**
* StepBuilderFactory adding {@link TraceStepExecutionListener}.
*
* @author Marcin Grzejszczak
* @since 3.1.0
*/
public class TraceStepBuilderFactory extends StepBuilderFactory {
private final BeanFactory beanFactory;
private final StepBuilderFactory delegate;
private Tracer tracer;
public TraceStepBuilderFactory(BeanFactory beanFactory, StepBuilderFactory delegate) {
super(null, null);
this.beanFactory = beanFactory;
this.delegate = delegate;
}
@Override
public StepBuilder get(String name) {
return this.delegate.get(name).listener(new TraceStepExecutionListener(tracer()));
}
private Tracer tracer() {
if (this.tracer == null) {
this.tracer = this.beanFactory.getBean(Tracer.class);
}
return this.tracer;
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.batch;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.SpanAndScope;
import org.springframework.cloud.sleuth.Tracer;
class TraceStepExecutionListener implements StepExecutionListener {
private final Tracer tracer;
private static Map<StepExecution, SpanAndScope> SPANS = new ConcurrentHashMap<>();
TraceStepExecutionListener(Tracer tracer) {
this.tracer = tracer;
}
@Override
public void beforeStep(StepExecution stepExecution) {
Span span = this.tracer.nextSpan().name(stepExecution.getStepName());
Tracer.SpanInScope spanInScope = this.tracer.withSpan(span.start());
SPANS.put(stepExecution, new SpanAndScope(span, spanInScope));
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
SpanAndScope spanAndScope = SPANS.remove(stepExecution);
List<Throwable> throwables = stepExecution.getFailureExceptions();
Span span = spanAndScope.getSpan();
span.tag("batch.step.name", stepExecution.getStepName());
span.tag("batch.job.executionId", String.valueOf(stepExecution.getJobExecutionId()));
span.tag("batch.step.executionId", String.valueOf(stepExecution.getId()));
span.tag("batch.step.type", stepExecution.getExecutionContext().getString(Step.STEP_TYPE_KEY));
Tracer.SpanInScope scope = spanAndScope.getScope();
if (!throwables.isEmpty()) {
span.error(mergedThrowables(throwables));
}
span.end();
scope.close();
return stepExecution.getExitStatus();
}
private IllegalStateException mergedThrowables(List<Throwable> throwables) {
return new IllegalStateException(
throwables.stream().map(Throwable::toString).collect(Collectors.joining("\n")));
}
}

View File

@@ -1,75 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
~
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-sleuth-tests-brave</artifactId>
<packaging>pom</packaging>
<name>Spring Cloud Sleuth Brave Tests</name>
<description>Spring Cloud Sleuth Brave Tests</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-tests</artifactId>
<version>3.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<modules>
<module>spring-cloud-sleuth-instrumentation-annotation-tests</module>
<module>spring-cloud-sleuth-instrumentation-async-tests</module>
<module>spring-cloud-sleuth-instrumentation-baggage-tests</module>
<module>spring-cloud-sleuth-instrumentation-config-server-tests</module>
<module>spring-cloud-sleuth-instrumentation-circuitbreaker-tests</module>
<module>spring-cloud-sleuth-instrumentation-circuitbreaker-reactive-tests</module>
<module>spring-cloud-sleuth-instrumentation-feign-tests</module>
<module>spring-cloud-sleuth-instrumentation-gateway-tests</module>
<module>spring-cloud-sleuth-instrumentation-grpc-tests</module>
<module>spring-cloud-sleuth-instrumentation-kafka-tests</module>
<module>spring-cloud-sleuth-instrumentation-lettuce-tests</module>
<module>spring-cloud-sleuth-instrumentation-messaging-tests</module>
<module>spring-cloud-sleuth-instrumentation-mvc-tests</module>
<module>spring-cloud-sleuth-instrumentation-quartz-tests</module>
<module>spring-cloud-sleuth-instrumentation-reactor-tests</module>
<module>spring-cloud-sleuth-instrumentation-rxjava-tests</module>
<module>spring-cloud-sleuth-instrumentation-scheduling-tests</module>
<module>spring-cloud-sleuth-instrumentation-task-tests</module>
<module>spring-cloud-sleuth-instrumentation-webflux-tests</module>
<module>spring-cloud-sleuth-instrumentation-rsocket-tests</module>
<module>spring-cloud-sleuth-zipkin-tests</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
~
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-sleuth-tests-brave</artifactId>
<packaging>pom</packaging>
<name>Spring Cloud Sleuth Brave Tests</name>
<description>Spring Cloud Sleuth Brave Tests</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-tests</artifactId>
<version>3.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<modules>
<module>spring-cloud-sleuth-instrumentation-annotation-tests</module>
<module>spring-cloud-sleuth-instrumentation-async-tests</module>
<module>spring-cloud-sleuth-instrumentation-baggage-tests</module>
<module>spring-cloud-sleuth-instrumentation-batch-tests</module>
<module>spring-cloud-sleuth-instrumentation-config-server-tests</module>
<module>spring-cloud-sleuth-instrumentation-circuitbreaker-tests</module>
<module>spring-cloud-sleuth-instrumentation-circuitbreaker-reactive-tests</module>
<module>spring-cloud-sleuth-instrumentation-feign-tests</module>
<module>spring-cloud-sleuth-instrumentation-gateway-tests</module>
<module>spring-cloud-sleuth-instrumentation-grpc-tests</module>
<module>spring-cloud-sleuth-instrumentation-kafka-tests</module>
<module>spring-cloud-sleuth-instrumentation-lettuce-tests</module>
<module>spring-cloud-sleuth-instrumentation-messaging-tests</module>
<module>spring-cloud-sleuth-instrumentation-mvc-tests</module>
<module>spring-cloud-sleuth-instrumentation-quartz-tests</module>
<module>spring-cloud-sleuth-instrumentation-reactor-tests</module>
<module>spring-cloud-sleuth-instrumentation-rxjava-tests</module>
<module>spring-cloud-sleuth-instrumentation-scheduling-tests</module>
<module>spring-cloud-sleuth-instrumentation-task-tests</module>
<module>spring-cloud-sleuth-instrumentation-webflux-tests</module>
<module>spring-cloud-sleuth-instrumentation-rsocket-tests</module>
<module>spring-cloud-sleuth-zipkin-tests</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<!--skip deploy (this is just a test module) -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
~
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-sleuth-instrumentation-batch-tests</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Sleuth Brave Batch Instrumentation Tests</name>
<description>Spring Cloud Sleuth Brave Batch Instrumentation Tests</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-tests-brave</artifactId>
<version>3.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<properties>
<sonar.skip>true</sonar.skip>
</properties>
<build>
<plugins>
<plugin>
<!--skip deploy -->
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-tests-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-tests</artifactId>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,54 @@
/*
* 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.batch;
import brave.sampler.Sampler;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
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 = BatchIntegrationTests.Config.class)
public class BatchIntegrationTests extends org.springframework.cloud.sleuth.instrument.batch.BatchIntegrationTests {
@Configuration(proxyBeanMethods = false)
@EnableBatchProcessing
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();
}
}
}

View File

@@ -0,0 +1,5 @@
logging.level.org.springframework.cloud: DEBUG
logging.level.com.netflix.discovery.InstanceInfoReplicator: ERROR
logging.level.org.springframework.cloud.sleuth.brave.instrument.web.client.feign: TRACE
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration

View File

@@ -64,6 +64,11 @@
<artifactId>spring-boot-starter-websocket</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>

View File

@@ -0,0 +1,102 @@
/*
* 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.batch;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.exporter.FinishedSpan;
import org.springframework.cloud.sleuth.test.TestSpanHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import static org.assertj.core.api.BDDAssertions.then;
@ContextConfiguration(classes = BatchIntegrationTests.TestConfig.class)
public abstract class BatchIntegrationTests {
private static final Log log = LogFactory.getLog(BatchIntegrationTests.class);
@Autowired
TestSpanHandler spans;
@Autowired
Tracer tracer;
@Autowired
StepBuilderFactory stepBuilderFactory;
@Autowired
JobBuilderFactory jobBuilderFactory;
@Autowired
JobLauncher jobLauncher;
@BeforeEach
public void setup() {
this.spans.clear();
}
@Test
public void should_pass_tracing_information_when_using_batch() throws Exception {
AtomicReference<Span> spanFromTasklet = new AtomicReference<>();
Job job = this.jobBuilderFactory.get("myJob")
.start(this.stepBuilderFactory.get("myTask").tasklet((stepContribution, chunkContext) -> {
log.info("Hello");
spanFromTasklet.set(this.tracer.currentSpan());
return RepeatStatus.FINISHED;
}).build()).build();
JobExecution jobExecution = this.jobLauncher.run(job, new JobParameters());
then(jobExecution.getExitStatus().getExitCode()).isEqualTo(ExitStatus.COMPLETED.getExitCode());
then(spanFromTasklet.get()).isNotNull();
List<FinishedSpan> spans = this.spans.reportedSpans();
then(spans).hasSize(2);
then(spans.stream().map(FinishedSpan::getTraceId).collect(Collectors.toSet())).hasSize(1);
then(spans.get(0).getName()).isEqualTo("myTask");
then(spans.get(1).getName()).isEqualTo("myJob");
then(this.tracer.currentSpan()).isNull();
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EnableBatchProcessing
public static class TestConfig {
}
}

View File

@@ -143,7 +143,7 @@ public abstract class FlatMapTests {
sender.port = port;
spans.clear();
Awaitility.await().atMost(5, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).untilAsserted(() -> {
Awaitility.await().atMost(15, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).untilAsserted(() -> {
// when
LOGGER.info("Start");
spans.clear();