diff --git a/docs/pom.xml b/docs/pom.xml
index 28627635..b38a7b7d 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -15,6 +15,12 @@
spring.cloud.task.*
deploy
1.5.0-alpha.16
+
+
+ 1.0.0-M4
+ ${maven.multiModuleProjectDirectory}/spring-cloud-task-core/
+ .*
+ ${maven.multiModuleProjectDirectory}/target/
@@ -49,6 +55,58 @@
org.codehaus.mojo
exec-maven-plugin
+
+
+ generate-metrics-metadata
+ prepare-package
+
+ java
+
+
+ io.micrometer.docs.metrics.DocsFromSources
+ true
+
+ ${micrometer-docs-generator.inputPath}
+ ${micrometer-docs-generator.inclusionPattern}
+ ${micrometer-docs-generator.outputPath}
+
+
+
+
+ generate-tracing-metadata
+ prepare-package
+
+ java
+
+
+ io.micrometer.docs.spans.DocsFromSources
+ true
+
+ ${micrometer-docs-generator.inputPath}
+ ${micrometer-docs-generator.inclusionPattern}
+ ${micrometer-docs-generator.outputPath}
+
+
+
+
+
+
+ io.micrometer
+
+ micrometer-docs-generator-spans
+ ${micrometer-docs-generator.version}
+
+ jar
+
+
+ io.micrometer
+
+ micrometer-docs-generator-metrics
+ ${micrometer-docs-generator.version}
+
+ jar
+
+
org.asciidoctor
diff --git a/docs/src/main/asciidoc/_observability.adoc b/docs/src/main/asciidoc/_observability.adoc
new file mode 100644
index 00000000..f31372d3
--- /dev/null
+++ b/docs/src/main/asciidoc/_observability.adoc
@@ -0,0 +1,8 @@
+:root-target: ../../../../target/
+
+[[observability]]
+== Observability metadata
+
+include::{root-target}_metrics.adoc[]
+
+include::{root-target}_spans.adoc[]
diff --git a/docs/src/main/asciidoc/appendix.adoc b/docs/src/main/asciidoc/appendix.adoc
index 34185ba3..5dee4c42 100644
--- a/docs/src/main/asciidoc/appendix.adoc
+++ b/docs/src/main/asciidoc/appendix.adoc
@@ -3,4 +3,7 @@
= Appendices
include::appendix-task-repository-schema.adoc[]
+
include::appendix-building-the-documentation.adoc[]
+
+include::_observability.adoc[]
diff --git a/spring-cloud-task-core/pom.xml b/spring-cloud-task-core/pom.xml
index 14c535df..4d77103d 100755
--- a/spring-cloud-task-core/pom.xml
+++ b/spring-cloud-task-core/pom.xml
@@ -109,10 +109,19 @@
spring-boot-autoconfigure-processor
true
+
+ org.springframework.boot
+ spring-boot-actuator-autoconfigure
+ true
+
io.micrometer
micrometer-core
+
+ io.micrometer
+ micrometer-observation
+
org.junit.jupiter
junit-jupiter-params
@@ -129,6 +138,41 @@
${jmock-version}
test
+
+ io.micrometer
+ micrometer-observation-test
+ test
+
+
+ io.micrometer
+ micrometer-test
+ test
+
+
+ io.micrometer
+ micrometer-tracing-test
+ test
+
+
+ io.micrometer
+ micrometer-tracing-bridge-brave
+ test
+
+
+ io.zipkin.brave
+ brave-tests
+ test
+
+
+ io.zipkin.reporter2
+ zipkin-reporter-brave
+ test
+
+
+ io.zipkin.reporter2
+ zipkin-sender-urlconnection
+ test
+
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/DefaultTaskKeyValuesProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/DefaultTaskKeyValuesProvider.java
new file mode 100644
index 00000000..d37a75ab
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/DefaultTaskKeyValuesProvider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013-2022 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.task.configuration.observation;
+
+import io.micrometer.common.KeyValues;
+import io.micrometer.observation.Observation;
+
+/**
+ * {@link Observation.KeyValuesProvider} for Spring Cloud Task.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.0.0
+ */
+public class DefaultTaskKeyValuesProvider implements TaskKeyValuesProvider {
+
+ @Override
+ public KeyValues getLowCardinalityKeyValues(TaskObservationContext context) {
+ return KeyValues.of(TaskDocumentedObservation.TaskRunnerTags.BEAN_NAME.of(context.getBeanName()));
+ }
+}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunner.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunner.java
new file mode 100644
index 00000000..b143912f
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunner.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018-2022 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.task.configuration.observation;
+
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationRegistry;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+
+/**
+ * Observed representation of a {@link ApplicationRunner}.
+ *
+ * @author Marcin Grzejszczak
+ */
+class ObservationApplicationRunner implements ApplicationRunner, Observation.KeyValuesProviderAware {
+
+ private final BeanFactory beanFactory;
+
+ private final ApplicationRunner delegate;
+
+ private final String beanName;
+
+ private ObservationRegistry registry;
+
+ private TaskKeyValuesProvider keyValuesProvider = new DefaultTaskKeyValuesProvider();
+
+ ObservationApplicationRunner(BeanFactory beanFactory, ApplicationRunner delegate, String beanName) {
+ this.beanFactory = beanFactory;
+ this.delegate = delegate;
+ this.beanName = beanName;
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ TaskObservationContext context = new TaskObservationContext(this.beanName);
+ Observation observation = TaskDocumentedObservation.TASK_RUNNER_OBSERVATION.observation(registry(), context)
+ .contextualName(this.beanName)
+ .keyValuesProvider(this.keyValuesProvider);
+ try (Observation.Scope scope = observation.start().openScope()) {
+ this.delegate.run(args);
+ }
+ catch (Exception error) {
+ observation.error(error);
+ throw error;
+ }
+ finally {
+ observation.stop();
+ }
+ }
+
+ private ObservationRegistry registry() {
+ if (this.registry == null) {
+ this.registry = this.beanFactory.getBean(ObservationRegistry.class);
+ }
+ return this.registry;
+ }
+
+ @Override
+ public void setKeyValuesProvider(TaskKeyValuesProvider keyValuesProvider) {
+ this.keyValuesProvider = keyValuesProvider;
+ }
+}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunnerBeanPostProcessor.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunnerBeanPostProcessor.java
new file mode 100644
index 00000000..eee9ab1e
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationApplicationRunnerBeanPostProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013-2022 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.task.configuration.observation;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.ApplicationRunner;
+
+/**
+ * Registers beans related to task scheduling.
+ *
+ * @author Marcin Grzejszczak
+ */
+class ObservationApplicationRunnerBeanPostProcessor implements BeanPostProcessor {
+
+ private final BeanFactory beanFactory;
+
+ ObservationApplicationRunnerBeanPostProcessor(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof ApplicationRunner applicationRunner && !(bean instanceof ObservationApplicationRunner)) {
+ return new ObservationApplicationRunner(this.beanFactory, applicationRunner, beanName);
+ }
+ return bean;
+ }
+
+}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunner.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunner.java
new file mode 100644
index 00000000..14801f69
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunner.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018-2022 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.task.configuration.observation;
+
+import io.micrometer.observation.Observation;
+import io.micrometer.observation.ObservationRegistry;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.boot.CommandLineRunner;
+
+/**
+ * Observed representation of a {@link CommandLineRunner}.
+ *
+ * @author Marcin Grzejszczak
+ */
+class ObservationCommandLineRunner implements CommandLineRunner, Observation.KeyValuesProviderAware {
+
+ private final BeanFactory beanFactory;
+
+ private final CommandLineRunner delegate;
+
+ private final String beanName;
+
+ private ObservationRegistry registry;
+
+ private TaskKeyValuesProvider keyValuesProvider = new DefaultTaskKeyValuesProvider();
+
+ ObservationCommandLineRunner(BeanFactory beanFactory, CommandLineRunner delegate, String beanName) {
+ this.beanFactory = beanFactory;
+ this.delegate = delegate;
+ this.beanName = beanName;
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ TaskObservationContext context = new TaskObservationContext(this.beanName);
+ Observation observation = TaskDocumentedObservation.TASK_RUNNER_OBSERVATION.observation(registry(), context)
+ .contextualName(this.beanName)
+ .keyValuesProvider(this.keyValuesProvider);
+ try (Observation.Scope scope = observation.start().openScope()) {
+ this.delegate.run(args);
+ }
+ catch (Exception error) {
+ observation.error(error);
+ throw error;
+ }
+ finally {
+ observation.stop();
+ }
+ }
+
+ private ObservationRegistry registry() {
+ if (this.registry == null) {
+ this.registry = this.beanFactory.getBean(ObservationRegistry.class);
+ }
+ return this.registry;
+ }
+
+ @Override
+ public void setKeyValuesProvider(TaskKeyValuesProvider keyValuesProvider) {
+ this.keyValuesProvider = keyValuesProvider;
+ }
+}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunnerBeanPostProcessor.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunnerBeanPostProcessor.java
new file mode 100644
index 00000000..93350416
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationCommandLineRunnerBeanPostProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013-2022 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.task.configuration.observation;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.CommandLineRunner;
+
+/**
+ * Registers beans related to task scheduling.
+ *
+ * @author Marcin Grzejszczak
+ */
+class ObservationCommandLineRunnerBeanPostProcessor implements BeanPostProcessor {
+
+ private final BeanFactory beanFactory;
+
+ ObservationCommandLineRunnerBeanPostProcessor(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof CommandLineRunner commandLineRunner && !(bean instanceof ObservationCommandLineRunner)) {
+ return new ObservationCommandLineRunner(this.beanFactory, commandLineRunner, beanName);
+ }
+ return bean;
+ }
+
+}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationTaskAutoConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationTaskAutoConfiguration.java
new file mode 100644
index 00000000..3d3eb2f5
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/ObservationTaskAutoConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013-2022 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.task.configuration.observation;
+
+import io.micrometer.observation.ObservationRegistry;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
+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.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
+ * Auto-configuration} that registers instrumentation for Spring Cloud Task.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.0.0
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnClass(ObservationRegistry.class)
+@ConditionalOnProperty(value = "spring.cloud.task.observation.enabled", matchIfMissing = true)
+@ConditionalOnBean(ObservationRegistry.class)
+@AutoConfigureAfter(ObservationAutoConfiguration.class)
+public class ObservationTaskAutoConfiguration {
+
+ @Bean
+ static ObservationCommandLineRunnerBeanPostProcessor observedCommandLineRunnerBeanPostProcessor(BeanFactory beanFactory) {
+ return new ObservationCommandLineRunnerBeanPostProcessor(beanFactory);
+ }
+
+ @Bean
+ static ObservationApplicationRunnerBeanPostProcessor observedApplicationRunnerBeanPostProcessor(BeanFactory beanFactory) {
+ return new ObservationApplicationRunnerBeanPostProcessor(beanFactory);
+ }
+
+}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskDocumentedObservation.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskDocumentedObservation.java
new file mode 100644
index 00000000..80747bc6
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskDocumentedObservation.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013-2022 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.task.configuration.observation;
+
+import io.micrometer.common.docs.KeyName;
+import io.micrometer.observation.docs.DocumentedObservation;
+
+enum TaskDocumentedObservation implements DocumentedObservation {
+
+ /**
+ * Observation created when a task runner is executed.
+ */
+ TASK_RUNNER_OBSERVATION {
+ @Override
+ public String getName() {
+ return "spring.cloud.task.runner";
+ }
+
+ @Override
+ public KeyName[] getLowCardinalityKeyNames() {
+ return TaskRunnerTags.values();
+ }
+
+ @Override
+ public String getPrefix() {
+ return "spring.cloud.task";
+ }
+ };
+
+ /**
+ * Key names for Spring Cloud Task Command / Application runners.
+ */
+ enum TaskRunnerTags implements KeyName {
+
+ /**
+ * Name of the bean that was executed by Spring Cloud Task.
+ */
+ BEAN_NAME {
+ @Override
+ public String getKeyName() {
+ return "spring.cloud.task.runner.bean-name";
+ }
+ }
+ }
+}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskKeyValuesProvider.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskKeyValuesProvider.java
new file mode 100644
index 00000000..687438c4
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskKeyValuesProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013-2022 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.task.configuration.observation;
+
+import io.micrometer.observation.Observation;
+
+/**
+ * {@link Observation.KeyValuesProvider} for Spring Cloud Task.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.0.0
+ */
+public interface TaskKeyValuesProvider extends Observation.KeyValuesProvider {
+
+ @Override
+ default boolean supportsContext(Observation.Context context) {
+ return context instanceof TaskObservationContext;
+ }
+}
diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskObservationContext.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskObservationContext.java
new file mode 100644
index 00000000..5a74a117
--- /dev/null
+++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskObservationContext.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013-2022 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.task.configuration.observation;
+
+import io.micrometer.observation.Observation;
+
+/**
+ * {@link Observation.Context} for Spring Cloud Task.
+ *
+ * @author Marcin Grzejszczak
+ * @since 3.0.0
+ */
+public class TaskObservationContext extends Observation.Context {
+
+ private final String beanName;
+
+ public TaskObservationContext(String beanName) {
+ this.beanName = beanName;
+ }
+
+ public String getBeanName() {
+ return beanName;
+ }
+}
diff --git a/spring-cloud-task-core/src/main/resources/META-INF/spring.factories b/spring-cloud-task-core/src/main/resources/META-INF/spring.factories
index e0401a79..76500f5a 100644
--- a/spring-cloud-task-core/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-task-core/src/main/resources/META-INF/spring.factories
@@ -1,5 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.task.configuration.SingleTaskConfiguration,\
org.springframework.cloud.task.configuration.SimpleTaskAutoConfiguration,\
-org.springframework.cloud.task.configuration.MetricsAutoConfiguration\
+org.springframework.cloud.task.configuration.MetricsAutoConfiguration,\
+org.springframework.cloud.task.configuration.observation.ObservationTaskAutoConfiguration
diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/configuration/observation/ObservationIntegrationTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/configuration/observation/ObservationIntegrationTests.java
new file mode 100644
index 00000000..b56ec25a
--- /dev/null
+++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/configuration/observation/ObservationIntegrationTests.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017-2022 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.task.configuration.observation;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import brave.handler.SpanHandler;
+import brave.sampler.Sampler;
+import brave.test.TestSpanHandler;
+import io.micrometer.common.KeyValues;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.tck.MeterRegistryAssert;
+import io.micrometer.tracing.Tracer;
+import io.micrometer.tracing.brave.bridge.BraveFinishedSpan;
+import io.micrometer.tracing.exporter.FinishedSpan;
+import io.micrometer.tracing.test.simple.SpansAssert;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import zipkin2.Span;
+import zipkin2.reporter.Reporter;
+import zipkin2.reporter.brave.ZipkinSpanHandler;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.task.configuration.EnableTask;
+import org.springframework.cloud.task.configuration.SimpleTaskAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@SpringBootTest(classes = ObservationIntegrationTests.Config.class)
+class ObservationIntegrationTests {
+
+ @Autowired
+ TestSpanHandler testSpanHandler;
+
+ @Autowired
+ MeterRegistry meterRegistry;
+
+ @Test
+ void testSuccessfulObservation() {
+ List finishedSpans = finishedSpans();
+
+ SpansAssert.then(finishedSpans)
+ .thenASpanWithNameEqualTo("my-command-line-runner")
+ .hasTag("spring.cloud.task.runner.bean-name", "myCommandLineRunner")
+ .backToSpans()
+ .thenASpanWithNameEqualTo("my-application-runner")
+ .hasTag("spring.cloud.task.runner.bean-name", "myApplicationRunner");
+ MeterRegistryAssert.then(this.meterRegistry)
+ .hasTimerWithNameAndTags("spring.cloud.task.runner", KeyValues.of("spring.cloud.task.runner.bean-name", "myCommandLineRunner"))
+ .hasTimerWithNameAndTags("spring.cloud.task.runner", KeyValues.of("spring.cloud.task.runner.bean-name", "myApplicationRunner"));
+ }
+
+ private List finishedSpans() {
+ return this.testSpanHandler.spans().stream().map(BraveFinishedSpan::fromBrave).collect(Collectors.toList());
+ }
+
+ @Configuration
+ @ImportAutoConfiguration({SimpleTaskAutoConfiguration.class, ObservationAutoConfiguration.class, ObservationTaskAutoConfiguration.class, BraveAutoConfiguration.class, MicrometerTracingAutoConfiguration.class, MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, ZipkinAutoConfiguration.class})
+ @EnableTask
+ static class Config {
+ private static final Logger log = LoggerFactory.getLogger(Config.class);
+
+ @Bean
+ TestSpanHandler testSpanHandler() {
+ return new TestSpanHandler();
+ }
+
+ @Bean
+ SpanHandler zipkinSpanHandler(Reporter spanReporter) {
+ return ZipkinSpanHandler.newBuilder(spanReporter).build();
+ }
+
+ @Bean
+ Sampler sampler() {
+ return Sampler.ALWAYS_SAMPLE;
+ }
+
+ @Bean
+ CommandLineRunner myCommandLineRunner(Tracer tracer) {
+ return args -> log.info(" Hello from command line runner", tracer.currentSpan().context().traceId());
+ }
+
+ @Bean
+ ApplicationRunner myApplicationRunner(Tracer tracer) {
+ return args -> log.info(" Hello from application runner", tracer.currentSpan().context().traceId());
+ }
+ }
+}
diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml
new file mode 100644
index 00000000..ab55cdec
--- /dev/null
+++ b/src/checkstyle/checkstyle-suppressions.xml
@@ -0,0 +1,7 @@
+
+
+
+
+