First pass on observation

This commit is contained in:
Marcin Grzejszczak
2022-05-11 11:22:01 +02:00
parent 1284c80c5e
commit 50423b6909
14 changed files with 794 additions and 1 deletions

View File

@@ -109,10 +109,19 @@
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
@@ -129,6 +138,41 @@
<version>${jmock-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-tests</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-sender-urlconnection</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,34 @@
/*
* 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.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()));
}
}

View File

@@ -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.task.configuration.observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Value;
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
ObservedTaskExecutionListener traceTaskExecutionListener(ObservationRegistry registry,
@Value("${spring.application.name:default}") String appName) {
return new ObservedTaskExecutionListener(registry, appName);
}
@Bean
static ObservedCommandLineRunnerBeanPostProcessor observedCommandLineRunnerBeanPostProcessor(BeanFactory beanFactory) {
return new ObservedCommandLineRunnerBeanPostProcessor(beanFactory);
}
@Bean
static ObservedApplicationRunnerBeanPostProcessor observedApplicationRunnerBeanPostProcessor(BeanFactory beanFactory) {
return new ObservedApplicationRunnerBeanPostProcessor(beanFactory);
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.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 ObservedApplicationRunner implements ApplicationRunner, Observation.KeyValuesProviderAware<TaskKeyValuesProvider> {
private final BeanFactory beanFactory;
private final ApplicationRunner delegate;
private final String beanName;
private ObservationRegistry registry;
private TaskKeyValuesProvider keyValuesProvider = new DefaultTaskKeyValuesProvider();
ObservedApplicationRunner(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;
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.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 ObservedApplicationRunnerBeanPostProcessor implements BeanPostProcessor {
private final BeanFactory beanFactory;
ObservedApplicationRunnerBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ApplicationRunner applicationRunner && !(bean instanceof ObservedApplicationRunner)) {
return new ObservedApplicationRunner(this.beanFactory, applicationRunner, beanName);
}
return bean;
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.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 ObservedCommandLineRunner implements CommandLineRunner, Observation.KeyValuesProviderAware<TaskKeyValuesProvider> {
private final BeanFactory beanFactory;
private final CommandLineRunner delegate;
private final String beanName;
private ObservationRegistry registry;
private TaskKeyValuesProvider keyValuesProvider = new DefaultTaskKeyValuesProvider();
ObservedCommandLineRunner(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;
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.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 ObservedCommandLineRunnerBeanPostProcessor implements BeanPostProcessor {
private final BeanFactory beanFactory;
ObservedCommandLineRunnerBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof CommandLineRunner commandLineRunner && !(bean instanceof ObservedCommandLineRunner)) {
return new ObservedCommandLineRunner(this.beanFactory, commandLineRunner, beanName);
}
return bean;
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.task.configuration.observation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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
*/
class ObservedTaskExecutionListener implements TaskExecutionListener, Ordered {
private static final Log log = LogFactory.getLog(ObservedTaskExecutionListener.class);
private final ObservationRegistry registry;
private final String projectName;
ObservedTaskExecutionListener(ObservationRegistry registry, String projectName) {
this.registry = registry;
this.projectName = projectName;
}
@Override
public void onTaskStartup(TaskExecution taskExecution) {
Observation observation = TaskDocumentedObservation.TASK_EXECUTION_LISTENER_OBSERVATION.observation(this.registry)
.contextualName(this.projectName)
.start();
observation.openScope();
if (log.isDebugEnabled()) {
log.debug("Put the observation [" + observation + "] in scope");
}
}
@Override
public void onTaskEnd(TaskExecution taskExecution) {
Observation.Scope scope = this.registry.getCurrentObservationScope();
if (scope != null) {
scope.close();
scope.getCurrentObservation().stop();
if (log.isDebugEnabled()) {
log.debug("Removed the [" + scope.getCurrentObservation() + "] from thread local");
}
}
}
@Override
public void onTaskFailed(TaskExecution taskExecution, Throwable throwable) {
Observation.Scope scope = this.registry.getCurrentObservationScope();
if (scope != null) {
Observation observation = scope.getCurrentObservation();
observation.error(throwable);
onTaskEnd(taskExecution);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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.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";
}
},
/**
* Observation created within the lifecycle of a task.
*/
TASK_EXECUTION_LISTENER_OBSERVATION {
@Override
public String getName() {
return "spring.cloud.task.execution";
}
@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";
}
}
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.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<TaskObservationContext> {
@Override
default boolean supportsContext(Observation.Context context) {
return context instanceof TaskObservationContext;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.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;
}
}

View File

@@ -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

View File

@@ -0,0 +1,111 @@
/*
* 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.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<FinishedSpan> 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<FinishedSpan> finishedSpans() {
return this.testSpanHandler.spans().stream().map(BraveFinishedSpan::fromBrave).collect(Collectors.toList());
}
@Configuration
@ImportAutoConfiguration({ObservationAutoConfiguration.class, ObservationTaskAutoConfiguration.class, BraveAutoConfiguration.class, MicrometerTracingAutoConfiguration.class, MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, ZipkinAutoConfiguration.class})
static class Config {
private static final Logger log = LoggerFactory.getLogger(Config.class);
@Bean
TestSpanHandler testSpanHandler() {
return new TestSpanHandler();
}
@Bean
SpanHandler zipkinSpanHandler(Reporter<Span> spanReporter) {
return ZipkinSpanHandler.newBuilder(spanReporter).build();
}
@Bean
Sampler sampler() {
return Sampler.ALWAYS_SAMPLE;
}
@Bean
CommandLineRunner myCommandLineRunner(Tracer tracer) {
return args -> log.info("<TRACE:{}> Hello from command line runner", tracer.currentSpan().context().traceId());
}
@Bean
ApplicationRunner myApplicationRunner(Tracer tracer) {
return args -> log.info("<TRACE:{}> Hello from application runner", tracer.currentSpan().context().traceId());
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.Date;
import java.util.List;
import io.micrometer.core.tck.TestObservationRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.task.repository.TaskExecution;
import static io.micrometer.core.tck.TestObservationRegistryAssert.then;
class ObservedTaskExecutionListenerTests {
@Test
void testSuccessfulObservation() {
TestObservationRegistry registry = TestObservationRegistry.create();
ObservedTaskExecutionListener listener = new ObservedTaskExecutionListener(registry, "my-project");
TaskExecution taskExecution = taskExecution();
listener.onTaskStartup(taskExecution);
listener.onTaskEnd(taskExecution);
then(registry)
.hasSingleObservationThat()
.hasNameEqualTo("spring.cloud.task.execution")
.hasContextualNameEqualTo("my-project");
}
@Test
void testErrorObservation() {
TestObservationRegistry registry = TestObservationRegistry.create();
ObservedTaskExecutionListener listener = new ObservedTaskExecutionListener(registry, "my-project");
TaskExecution taskExecution = taskExecution();
listener.onTaskStartup(taskExecution);
listener.onTaskFailed(taskExecution, new RuntimeException("error"));
then(registry)
.hasSingleObservationThat()
.hasNameEqualTo("spring.cloud.task.execution")
.hasContextualNameEqualTo("my-project")
.thenThrowable()
.hasMessage("error");
}
private TaskExecution taskExecution() {
return new TaskExecution(1L, 1, "task", new Date(), new Date(), "bye", List.of("arg"), "boom", "id");
}
}