From 2f0f78a2c8655507df158dcd1e1b8175cd9fd7fd Mon Sep 17 00:00:00 2001 From: Glenn Renfro Date: Thu, 3 Mar 2022 07:58:29 -0500 Subject: [PATCH] Updating task metrics to micrometer 1.10 Changes include: * Use Observable instead of metrics * Update the setting of lowCardinalityTag to the correct events. * Update the docs * Remove EXTERNAL_EXECUTION_ID * Utilize registry provided by boot instead of globalregistry * Set the handler until Boot does this as shown on line 80 of TaskLifecycleConfiguration. * Uses Observability long task timer Updated to latest changes in Micrometer Observability Updated based on code review and api change Finished removing the metrics registry Updated based on code review Readded cloud foundry keys to observations Renamed metrics to observations where applicable Updated metric sample to be observation sample Updated to set defaults and migrate tests to infrastructure Updated based on code review Remove duplicate keyvalues from task observation Added DocumentedObservation to create the observation Migrated code to use the LowerCardinality KeyNames from the DocumentedObservation Test cleanup Updated code based on code review Exception is handled by the error in the observation vs logging it as a task metric key value Updated to use ObservationConvention Updated to handle user defined convention vs default Removed the snapshot versions of observations Removed unnecessary dependencies Checkpoint tests fail after re-adding 1.10-SNAPSHOT Replaced TimeObservationHandler with DefaultMeterObservationHandler Updated to set micrometer to latest milestone Updated to rename TaskValuesProvider variables to ObservationConvention --- docs/src/main/asciidoc/features.adoc | 18 + spring-cloud-task-core/pom.xml | 27 +- .../MetricsAutoConfiguration.java | 136 ------- .../SimpleTaskAutoConfiguration.java | 8 + .../TaskLifecycleConfiguration.java | 20 +- .../TaskObservationCloudKeyValues.java | 106 +++++ .../DefaultTaskObservationConvention.java | 2 +- .../ObservationApplicationRunner.java | 2 + .../ObservationTaskAutoConfiguration.java | 3 - .../TaskObservationConvention.java | 2 +- ...ultTaskExecutionObservationConvention.java | 55 +++ .../listener/TaskExecutionObservation.java | 182 +++++++++ .../TaskExecutionObservationContext.java | 60 +++ .../TaskExecutionObservationConvention.java | 33 ++ .../task/listener/TaskLifecycleListener.java | 25 +- .../cloud/task/listener/TaskMetrics.java | 139 ------- .../cloud/task/listener/TaskObservations.java | 121 ++++++ .../main/resources/META-INF/spring.factories | 2 - .../micrometer/AbstractMicrometerTest.java | 134 ------- ...oundryMicrometerTagsConfigurationTest.java | 101 ----- ...MicrometerCommonTagsConfigurationTest.java | 77 ---- .../task/micrometer/TaskMetricsTests.java | 168 -------- .../micrometer/TaskObservationsTests.java | 367 ++++++++++++++++++ .../task/util/TestDefaultConfiguration.java | 9 +- spring-cloud-task-samples/pom.xml | 2 +- .../src/main/resources/application.properties | 6 - .../.gitignore | 0 .../.mvn/wrapper/maven-wrapper.jar | Bin .../.mvn/wrapper/maven-wrapper.properties | 0 .../README.adoc | 0 .../{task-metrics => task-observations}/mvnw | 0 .../mvnw.cmd | 0 .../pom.xml | 6 +- .../ObservationConfiguration.java} | 4 +- .../TaskObservationsApplication.java} | 23 +- .../src/main/resources/application.properties | 6 + .../TaskObservationsApplicationTests.java} | 13 +- spring-cloud-task-stream/pom.xml | 1 + 38 files changed, 1032 insertions(+), 826 deletions(-) delete mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/MetricsAutoConfiguration.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskObservationCloudKeyValues.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/DefaultTaskExecutionObservationConvention.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservation.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservationContext.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservationConvention.java delete mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskMetrics.java create mode 100644 spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskObservations.java delete mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/AbstractMicrometerTest.java delete mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/CloudFoundryMicrometerTagsConfigurationTest.java delete mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/SpringCloudTaskMicrometerCommonTagsConfigurationTest.java delete mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskMetricsTests.java create mode 100644 spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskObservationsTests.java delete mode 100644 spring-cloud-task-samples/task-metrics/src/main/resources/application.properties rename spring-cloud-task-samples/{task-metrics => task-observations}/.gitignore (100%) rename spring-cloud-task-samples/{task-metrics => task-observations}/.mvn/wrapper/maven-wrapper.jar (100%) rename spring-cloud-task-samples/{task-metrics => task-observations}/.mvn/wrapper/maven-wrapper.properties (100%) rename spring-cloud-task-samples/{task-metrics => task-observations}/README.adoc (100%) rename spring-cloud-task-samples/{task-metrics => task-observations}/mvnw (100%) rename spring-cloud-task-samples/{task-metrics => task-observations}/mvnw.cmd (100%) rename spring-cloud-task-samples/{task-metrics => task-observations}/pom.xml (95%) rename spring-cloud-task-samples/{task-metrics/src/main/java/io/spring/taskmetrics/MetricConfiguration.java => task-observations/src/main/java/io/spring/taskobservations/ObservationConfiguration.java} (92%) rename spring-cloud-task-samples/{task-metrics/src/main/java/io/spring/taskmetrics/TaskMetricsApplication.java => task-observations/src/main/java/io/spring/taskobservations/TaskObservationsApplication.java} (74%) create mode 100644 spring-cloud-task-samples/task-observations/src/main/resources/application.properties rename spring-cloud-task-samples/{task-metrics/src/test/java/io/spring/taskmetrics/TaskMetricsApplicationTests.java => task-observations/src/test/java/io/spring/taskobservations/TaskObservationsApplicationTests.java} (71%) diff --git a/docs/src/main/asciidoc/features.adoc b/docs/src/main/asciidoc/features.adoc index 0d0d4936..3250cd2a 100644 --- a/docs/src/main/asciidoc/features.adoc +++ b/docs/src/main/asciidoc/features.adoc @@ -448,3 +448,21 @@ In these cases the context is held open because a thread has been allocated set the `spring.cloud.task.closecontextEnabled` property to `true` when launching your task. This will close the application's context once the task is complete. Thus allowing the application to terminate. + +[[enable-task-metrics]] +=== Enable Task Metrics +Spring Cloud Task integrates with Micrometer and creates observations for the Tasks it executes. +To enable Task Observability integration, you must add `spring-boot-starter-actuator`, your preferred registry implementation (if you want to publish metrics), and micrometer-tracing (if you want to publish tracing data) to your task application. +An example maven set of dependencies to enable task observability and metrics using Influx would be: + +[source,xml] + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-influx + runtime + + diff --git a/spring-cloud-task-core/pom.xml b/spring-cloud-task-core/pom.xml index 4d77103d..2ef20c63 100755 --- a/spring-cloud-task-core/pom.xml +++ b/spring-cloud-task-core/pom.xml @@ -13,9 +13,6 @@ jar Spring Cloud Task Core Spring Cloud Task - - 1.49 - @@ -123,19 +120,8 @@ micrometer-observation - org.junit.jupiter - junit-jupiter-params - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.jmockit - jmockit - ${jmock-version} + io.micrometer + micrometer-test test @@ -144,8 +130,13 @@ test - io.micrometer - micrometer-test + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/MetricsAutoConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/MetricsAutoConfiguration.java deleted file mode 100644 index 2b4553f7..00000000 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/MetricsAutoConfiguration.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2019-2019 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; - -import java.util.Arrays; - -import io.micrometer.core.annotation.Timed; -import io.micrometer.core.instrument.Clock; -import io.micrometer.core.instrument.MeterRegistry; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.EnvironmentAware; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; - -/** - * Forked and simplified version of the - * org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration form - * spring-boot-actuator - * - * {@link EnableAutoConfiguration Auto-configuration} for Micrometer-based metrics. - * - * @author Michael Minella - * @since 2.2 - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass(Timed.class) -@AutoConfigureBefore(name = { - "org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration" }) -public class MetricsAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public Clock micrometerClockFork() { - return Clock.SYSTEM; - } - - @Bean - public static MeterRegistryPostProcessor meterRegistryPostProcessorFork() { - return new MeterRegistryPostProcessor(); - } - - static class MeterRegistryPostProcessor - implements BeanPostProcessor, EnvironmentAware { - - private Environment environment; - - @Value("${spring.cloud.task.name:unknown}") - private String taskName; - - @Value("${spring.cloud.task.executionid:unknown}") - private String taskExecutionId; - - @Value("${spring.cloud.task.external-execution-id:unknown}") - private String taskExternalExecutionId; - - @Value("${spring.cloud.task.parent-execution-id:unknown}") - private String taskParentExecutionId; - - @Value("${vcap.application.org_name:default}") - private String organizationName; - - @Value("${vcap.application.space_id:unknown}") - private String spaceId; - - @Value("${vcap.application.space_name:unknown}") - private String spaceName; - - @Value("${vcap.application.application_name:unknown}") - private String applicationName; - - @Value("${vcap.application.application_id:unknown}") - private String applicationId; - - @Value("${vcap.application.application_version:unknown}") - private String applicationVersion; - - @Value("${vcap.application.instance_index:0}") - private String instanceIndex; - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - - if (bean instanceof MeterRegistry) { - MeterRegistry registry = (MeterRegistry) bean; - - if (Arrays.asList(this.environment.getActiveProfiles()) - .contains("cloud")) { - registry.config().commonTags("cf.org.name", organizationName) - .commonTags("cf.space.id", spaceId) - .commonTags("cf.space.name", spaceName) - .commonTags("cf.app.id", applicationId) - .commonTags("cf.app.name", applicationName) - .commonTags("cf.app.version", applicationVersion) - .commonTags("cf.instance.index", instanceIndex); - } - - registry.config().commonTags("task.name", taskName) - .commonTags("task.execution.id", taskExecutionId) - .commonTags("task.external.execution.id", taskExternalExecutionId) - .commonTags("task.parent.execution.id", taskParentExecutionId); - } - - return bean; - } - - @Override - public void setEnvironment(Environment environment) { - this.environment = environment; - } - - } - -} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.java index c53a8c94..0a56db02 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/SimpleTaskAutoConfiguration.java @@ -38,6 +38,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Profile; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.util.CollectionUtils; @@ -113,6 +114,13 @@ public class SimpleTaskAutoConfiguration { return taskRepositoryInitializer; } + + @Bean + @Profile("cloud") + TaskObservationCloudKeyValues taskObservationCloudKeyValues() { + return new TaskObservationCloudKeyValues(); + } + /** * Determines the {@link TaskConfigurer} to use. */ diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskLifecycleConfiguration.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskLifecycleConfiguration.java index 4f823acd..c0c0d134 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskLifecycleConfiguration.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskLifecycleConfiguration.java @@ -16,6 +16,7 @@ package org.springframework.cloud.task.configuration; +import io.micrometer.observation.ObservationRegistry; import jakarta.annotation.PostConstruct; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -61,17 +62,27 @@ public class TaskLifecycleConfiguration { private boolean initialized = false; + private ObservationRegistry observationRegistry; + + private TaskObservationCloudKeyValues taskObservationCloudKeyValues; + @Autowired public TaskLifecycleConfiguration(TaskProperties taskProperties, - ConfigurableApplicationContext context, TaskRepository taskRepository, - TaskExplorer taskExplorer, TaskNameResolver taskNameResolver, - ObjectProvider applicationArguments) { + ConfigurableApplicationContext context, TaskRepository taskRepository, + TaskExplorer taskExplorer, TaskNameResolver taskNameResolver, + ObjectProvider applicationArguments, + @Autowired(required = false) ObservationRegistry observationRegistry, + @Autowired(required = false) TaskObservationCloudKeyValues taskObservationCloudKeyValues) { + this.taskProperties = taskProperties; this.context = context; this.taskRepository = taskRepository; this.taskExplorer = taskExplorer; this.taskNameResolver = taskNameResolver; this.applicationArguments = applicationArguments.getIfAvailable(); + this.observationRegistry = observationRegistry == null ? ObservationRegistry.NOOP : observationRegistry; + this.taskObservationCloudKeyValues = taskObservationCloudKeyValues; + } @Bean @@ -88,7 +99,8 @@ public class TaskLifecycleConfiguration { this.taskLifecycleListener = new TaskLifecycleListener(this.taskRepository, this.taskNameResolver, this.applicationArguments, this.taskExplorer, this.taskProperties, - new TaskListenerExecutorObjectFactory(this.context)); + new TaskListenerExecutorObjectFactory(this.context), + this.observationRegistry, taskObservationCloudKeyValues); this.initialized = true; } diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskObservationCloudKeyValues.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskObservationCloudKeyValues.java new file mode 100644 index 00000000..335c1a33 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/TaskObservationCloudKeyValues.java @@ -0,0 +1,106 @@ +/* + * Copyright 2022-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; + +import org.springframework.beans.factory.annotation.Value; + +/** + * Provides values for the {@link io.micrometer.common.KeyValues} for the task + * {@link io.micrometer.observation.Observation} when the cloud profile is active. + * + * @author Glenn Renfro + * @since 3.0 + */ +public class TaskObservationCloudKeyValues { + + @Value("${vcap.application.org_name:default}") + private String organizationName; + + @Value("${vcap.application.space_id:unknown}") + private String spaceId; + + @Value("${vcap.application.space_name:unknown}") + private String spaceName; + + @Value("${vcap.application.application_name:unknown}") + private String applicationName; + + @Value("${vcap.application.application_id:unknown}") + private String applicationId; + + @Value("${vcap.application.application_version:unknown}") + private String applicationVersion; + + @Value("${vcap.application.instance_index:0}") + private String instanceIndex; + + public String getOrganizationName() { + return organizationName; + } + + public void setOrganizationName(String organizationName) { + this.organizationName = organizationName; + } + + public String getSpaceId() { + return spaceId; + } + + public void setSpaceId(String spaceId) { + this.spaceId = spaceId; + } + + public String getSpaceName() { + return spaceName; + } + + public void setSpaceName(String spaceName) { + this.spaceName = spaceName; + } + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public String getApplicationVersion() { + return applicationVersion; + } + + public void setApplicationVersion(String applicationVersion) { + this.applicationVersion = applicationVersion; + } + + public String getInstanceIndex() { + return instanceIndex; + } + + public void setInstanceIndex(String instanceIndex) { + this.instanceIndex = instanceIndex; + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/DefaultTaskObservationConvention.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/DefaultTaskObservationConvention.java index 65f8250b..52c9b75c 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/DefaultTaskObservationConvention.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/DefaultTaskObservationConvention.java @@ -20,7 +20,7 @@ import io.micrometer.common.KeyValues; import io.micrometer.observation.Observation; /** - * {@link Observation.KeyValuesProvider} for Spring Cloud Task. + * {@link Observation.ObservationConvention} for Spring Cloud Task. * * @author Marcin Grzejszczak * @since 3.0.0 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 index 740bd78f..ef7b1f2a 100644 --- 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 @@ -53,6 +53,7 @@ class ObservationApplicationRunner implements ApplicationRunner { TaskObservationContext context = new TaskObservationContext(this.beanName); Observation observation = TaskDocumentedObservation.TASK_RUNNER_OBSERVATION.observation(this.taskObservationConvention, INSTANCE, context, registry()) .contextualName(this.beanName); + try (Observation.Scope scope = observation.start().openScope()) { this.delegate.run(args); } @@ -71,4 +72,5 @@ class ObservationApplicationRunner implements ApplicationRunner { } return this.registry; } + } 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 index 3d3eb2f5..e6cd84b3 100644 --- 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 @@ -19,8 +19,6 @@ 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; @@ -38,7 +36,6 @@ import org.springframework.context.annotation.Configuration; @ConditionalOnClass(ObservationRegistry.class) @ConditionalOnProperty(value = "spring.cloud.task.observation.enabled", matchIfMissing = true) @ConditionalOnBean(ObservationRegistry.class) -@AutoConfigureAfter(ObservationAutoConfiguration.class) public class ObservationTaskAutoConfiguration { @Bean diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskObservationConvention.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskObservationConvention.java index 013d0d2e..5fda2390 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskObservationConvention.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/configuration/observation/TaskObservationConvention.java @@ -19,7 +19,7 @@ package org.springframework.cloud.task.configuration.observation; import io.micrometer.observation.Observation; /** - * {@link Observation.KeyValuesProvider} for Spring Cloud Task. + * {@link Observation.ObservationConvention} for Spring Cloud Task. * * @author Marcin Grzejszczak * @since 3.0.0 diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/DefaultTaskExecutionObservationConvention.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/DefaultTaskExecutionObservationConvention.java new file mode 100644 index 00000000..046434d7 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/DefaultTaskExecutionObservationConvention.java @@ -0,0 +1,55 @@ +/* + * Copyright 2022-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.listener; + +import io.micrometer.common.KeyValues; + +import org.springframework.cloud.task.repository.TaskExecution; + +/** + * /** + * Default {@link TaskExecutionObservationConvention} implementation. + * + * @author Glenn Renfro + * @since 3.0.0 + */ +public class DefaultTaskExecutionObservationConvention implements TaskExecutionObservationConvention { + + @Override + public KeyValues getLowCardinalityKeyValues(TaskExecutionObservationContext context) { + return getKeyValuesForTaskExecution(context); + } + + @Override + public KeyValues getHighCardinalityKeyValues(TaskExecutionObservationContext context) { + return KeyValues.empty(); + } + + private KeyValues getKeyValuesForTaskExecution(TaskExecutionObservationContext context) { + TaskExecution execution = context.getTaskExecution(); + return KeyValues.of( + TaskExecutionObservation.TaskKeyValues.TASK_STATUS.getKeyName(), context.getStatus(), + TaskExecutionObservation.TaskKeyValues.TASK_EXIT_CODE.getKeyName(), String.valueOf(execution.getExitCode()), + TaskExecutionObservation.TaskKeyValues.TASK_EXECUTION_ID.getKeyName(), + String.valueOf(execution.getExecutionId())); + } + + @Override + public String getName() { + return "spring.cloud.task"; + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservation.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservation.java new file mode 100644 index 00000000..a1d55277 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservation.java @@ -0,0 +1,182 @@ +/* + * Copyright 2022-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.listener; + +import io.micrometer.common.docs.KeyName; +import io.micrometer.observation.Observation; +import io.micrometer.observation.docs.DocumentedObservation; + +/** + * Enumeration for task execution observations. + * + * @author Glenn Renfro + * @since 3.0.0 + */ +public enum TaskExecutionObservation implements DocumentedObservation { + /** + * Metrics created around a task execution. + */ + TASK_ACTIVE { + + @Override + public Class> getDefaultConvention() { + return DefaultTaskExecutionObservationConvention.class; + } + + public String getPrefix() { + return "spring.cloud.task"; + } + }; + @Override + public KeyName[] getLowCardinalityKeyNames() { + return TaskKeyValues.values(); + } + + public enum TaskKeyValues implements KeyName { + + /** + * Task name measurement. + */ + TASK_NAME { + @Override + public String getKeyName() { + return "spring.cloud.task.name"; + } + }, + + /** + * Task execution id. + */ + TASK_EXECUTION_ID { + @Override + public String getKeyName() { + return "spring.cloud.task.execution.id"; + } + }, + + /** + * Task parent execution id. + */ + TASK_PARENT_EXECUTION_ID { + @Override + public String getKeyName() { + return "spring.cloud.task.parent.execution.id"; + } + }, + + /** + * External execution id for task. + */ + TASK_EXTERNAL_EXECUTION_ID { + @Override + public String getKeyName() { + return "spring.cloud.task.external.execution.id"; + } + }, + /** + * Task exit code. + */ + TASK_EXIT_CODE { + @Override + public String getKeyName() { + return "spring.cloud.task.exit.code"; + } + }, + + /** + * task status. Can be either success or failure. + */ + TASK_STATUS { + @Override + public String getKeyName() { + return "spring.cloud.task.status"; + } + }, + + /** + * Organization Name for CF cloud. + */ + TASK_CF_ORG_NAME { + @Override + public String getKeyName() { + return "cf.org.name"; + } + }, + + /** + * Space id for CF cloud. + */ + TASK_CF_SPACE_ID { + @Override + public String getKeyName() { + return "cf.space.id"; + } + }, + + /** + * Space name for CF cloud. + */ + TASK_CF_SPACE_NAME { + @Override + public String getKeyName() { + return "cf.space.name"; + } + }, + + /** + * App name for CF cloud. + */ + TASK_CF_APP_NAME { + @Override + public String getKeyName() { + return "cf.app.name"; + } + }, + + /** + * App id for CF cloud. + */ + TASK_CF_APP_ID { + @Override + public String getKeyName() { + return "cf.app.id"; + } + }, + + /** + * App version for CF cloud. + */ + TASK_CF_APP_VERSION { + @Override + public String getKeyName() { + return "cf.app.version"; + } + }, + + /** + * Instance index for CF cloud. + */ + TASK_CF_INSTANCE_INDEX { + @Override + public String getKeyName() { + return "cf.instance.index"; + } + } + + } + +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservationContext.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservationContext.java new file mode 100644 index 00000000..41ac8c7e --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservationContext.java @@ -0,0 +1,60 @@ +/* + * Copyright 2022-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.listener; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationHandler; + +import org.springframework.cloud.task.repository.TaskExecution; + +/** + * A mutable holder of the {@link TaskExecution} required by a {@link ObservationHandler}. + * + * @author Glenn Renfro + * @since 3.0.0 + */ +public class TaskExecutionObservationContext extends Observation.Context { + private final TaskExecution taskExecution; + + private String exceptionMessage = "none"; + + private String status = "success"; + + public TaskExecutionObservationContext(TaskExecution taskExecution) { + this.taskExecution = taskExecution; + } + + public TaskExecution getTaskExecution() { + return taskExecution; + } + + public String getExceptionMessage() { + return exceptionMessage; + } + + public void setExceptionMessage(String exceptionMessage) { + this.exceptionMessage = exceptionMessage; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservationConvention.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservationConvention.java new file mode 100644 index 00000000..0ede4ff7 --- /dev/null +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskExecutionObservationConvention.java @@ -0,0 +1,33 @@ +/* + * Copyright 2022-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.listener; + +import io.micrometer.observation.Observation; + +/** + * {@link Observation.ObservationConvention} for {@link TaskExecutionObservationContext}. + * + * @author Glenn Renfro + * @since 3.0.0 + */ +public interface TaskExecutionObservationConvention extends Observation.ObservationConvention { + + @Override + default boolean supportsContext(Observation.Context context) { + return context instanceof TaskExecutionObservationContext; + } +} diff --git a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java index 0f982124..cf9d048f 100644 --- a/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java +++ b/spring-cloud-task-core/src/main/java/org/springframework/cloud/task/listener/TaskLifecycleListener.java @@ -26,6 +26,8 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -36,6 +38,7 @@ import org.springframework.boot.ExitCodeEvent; import org.springframework.boot.ExitCodeGenerator; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.cloud.task.configuration.TaskObservationCloudKeyValues; import org.springframework.cloud.task.configuration.TaskProperties; import org.springframework.cloud.task.repository.TaskExecution; import org.springframework.cloud.task.repository.TaskExplorer; @@ -88,7 +91,7 @@ public class TaskLifecycleListener implements ApplicationListener taskExecutionListenersFromContext; + @Autowired(required = false) + private Observation.ObservationConvention observationConvention; + private List taskExecutionListeners; private TaskExecution taskExecution; @@ -132,7 +138,9 @@ public class TaskLifecycleListener implements ApplicationListener startupListenerList = new ArrayList<>( this.taskExecutionListeners); @@ -338,7 +347,9 @@ public class TaskLifecycleListener implements ApplicationListener mockVcapServicesFromString(String serviceJson) { - final Map env = System.getenv(); - return new MockUp() { - @Mock - public String getenv(String name) { - if (name.equalsIgnoreCase("VCAP_SERVICES")) { - return serviceJson; - } - else { - return name.equalsIgnoreCase("VCAP_APPLICATION") - ? "{\"instance_id\":\"123\"}" : (String) env.get(name); - } - } - - @Mock - public Map getenv() { - Map finalMap = new HashMap(); - finalMap.putAll(env); - finalMap.put("VCAP_SERVICES", serviceJson); - return finalMap; - } - }; - } - - @SpringBootApplication - public static class AutoConfigurationApplication { - - public static void main(String[] args) { - SpringApplication.run(AutoConfigurationApplication.class, args); - } - - @Bean - @ConditionalOnMissingBean - public SimpleMeterRegistry simpleMeterRegistry(SimpleConfig config, Clock clock) { - return new SimpleMeterRegistry(config, clock); - } - - @Bean - @ConditionalOnMissingBean - public SimpleConfig simpleConfig() { - return key -> null; - } - - } - -} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/CloudFoundryMicrometerTagsConfigurationTest.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/CloudFoundryMicrometerTagsConfigurationTest.java deleted file mode 100644 index 010bb9ef..00000000 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/CloudFoundryMicrometerTagsConfigurationTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2019-2019 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.micrometer; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -@Nested -public class CloudFoundryMicrometerTagsConfigurationTest { - - @ActiveProfiles("cloud") - public static class ActiveCloudProfileDefaultValues extends AbstractMicrometerTest { - - @Test - public void testDefaultTagValues() { - assertThat(meter.getId().getTag("cf.org.name")).isEqualTo("default"); - assertThat(meter.getId().getTag("cf.space.id")).isEqualTo("unknown"); - assertThat(meter.getId().getTag("cf.space.name")).isEqualTo("unknown"); - assertThat(meter.getId().getTag("cf.app.name")).isEqualTo("unknown"); - assertThat(meter.getId().getTag("cf.app.id")).isEqualTo("unknown"); - assertThat(meter.getId().getTag("cf.app.version")).isEqualTo("unknown"); - assertThat(meter.getId().getTag("cf.instance.index")).isEqualTo("0"); - } - - } - - @TestPropertySource(properties = { "vcap.application.org_name=PivotalOrg", - "vcap.application.space_id=SpringSpaceId", - "vcap.application.space_name=SpringSpace", - "vcap.application.application_name=App123", - "vcap.application.application_id=123guid", - "vcap.application.application_version=2.0", - "vcap.application.instance_index=123" }) - @ActiveProfiles("cloud") - public static class ActiveCloudProfile extends AbstractMicrometerTest { - - @Test - public void testPresetTagValues() { - assertThat(meter.getId().getTag("cf.org.name")).isEqualTo("PivotalOrg"); - assertThat(meter.getId().getTag("cf.space.id")).isEqualTo("SpringSpaceId"); - assertThat(meter.getId().getTag("cf.space.name")).isEqualTo("SpringSpace"); - assertThat(meter.getId().getTag("cf.app.name")).isEqualTo("App123"); - assertThat(meter.getId().getTag("cf.app.id")).isEqualTo("123guid"); - assertThat(meter.getId().getTag("cf.app.version")).isEqualTo("2.0"); - assertThat(meter.getId().getTag("cf.instance.index")).isEqualTo("123"); - } - - } - - @TestPropertySource(properties = { "vcap.application.org_name=PivotalOrg", - "vcap.application.space_id=SpringSpaceId", - "vcap.application.space_name=SpringSpace", - "vcap.application.application_name=App123", - "vcap.application.application_id=123guid", - "vcap.application.application_version=2.0", - "vcap.application.instance_index=123" }) - public static class InactiveCloudProfile extends AbstractMicrometerTest { - - @Test - public void testDisabledTagValues() { - assertThat(meter.getId().getTag("cf.org.name")).isNull(); - assertThat(meter.getId().getTag("cf.space.id")).isNull(); - assertThat(meter.getId().getTag("cf.space.name")).isNull(); - assertThat(meter.getId().getTag("cf.app.name")).isNull(); - assertThat(meter.getId().getTag("cf.app.id")).isNull(); - assertThat(meter.getId().getTag("cf.app.version")).isNull(); - assertThat(meter.getId().getTag("cf.instance.index")).isNull(); - } - - } - - @TestPropertySource( - properties = { "spring.cloud.task.metrics.cf.tags.enabled=false" }) - @ActiveProfiles("cloud") - public static class ActiveCloudProfileDisabledProperty extends InactiveCloudProfile { - - } - -} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/SpringCloudTaskMicrometerCommonTagsConfigurationTest.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/SpringCloudTaskMicrometerCommonTagsConfigurationTest.java deleted file mode 100644 index 19f74d62..00000000 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/SpringCloudTaskMicrometerCommonTagsConfigurationTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2019-2019 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.micrometer; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import org.springframework.test.context.TestPropertySource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -@Nested -public class SpringCloudTaskMicrometerCommonTagsConfigurationTest { - - public static class TestDefaultTagValues extends AbstractMicrometerTest { - - @Test - public void testDefaultTagValues() { - assertThat(meter.getId().getTag("task.name")).isEqualTo("unknown"); - assertThat(meter.getId().getTag("task.execution.id")).isEqualTo("unknown"); - assertThat(meter.getId().getTag("task.parent.execution.id")) - .isEqualTo("unknown"); - assertThat(meter.getId().getTag("task.external.execution.id")) - .isEqualTo("unknown"); - } - - } - - @TestPropertySource(properties = { "spring.cloud.task.name=myTask", - "spring.cloud.task.executionid=123", - "spring.cloud.task.parent-execution-id=999", - "spring.cloud.task.external-execution-id=696" }) - public static class TestPresetTagValues extends AbstractMicrometerTest { - - @Test - public void testPresetTagValues() { - assertThat(meter.getId().getTag("task.name")).isEqualTo("myTask"); - assertThat(meter.getId().getTag("task.execution.id")).isEqualTo("123"); - assertThat(meter.getId().getTag("task.parent.execution.id")).isEqualTo("999"); - assertThat(meter.getId().getTag("task.external.execution.id")) - .isEqualTo("696"); - } - - } - - @TestPropertySource( - properties = { "spring.cloud.task.metrics.common.tags.enabled=false" }) - public static class TestDisabledTagValues extends AbstractMicrometerTest { - - @Test - public void testDefaultTagValues() { - assertThat(meter.getId().getTag("task.name")).isNull(); - assertThat(meter.getId().getTag("task.execution.id")).isNull(); - assertThat(meter.getId().getTag("task.parent.execution.id")).isNull(); - assertThat(meter.getId().getTag("task.external.execution.id")).isNull(); - } - - } - -} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskMetricsTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskMetricsTests.java deleted file mode 100644 index 9cb74c3a..00000000 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskMetricsTests.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2019-2019 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.micrometer; - -import java.util.ArrayList; -import java.util.Date; - -import io.micrometer.core.instrument.LongTaskTimer; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Timer; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.task.listener.TaskMetrics; -import org.springframework.cloud.task.repository.TaskExecution; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -public class TaskMetricsTests { - - private TaskMetrics taskMetrics; - - private SimpleMeterRegistry simpleMeterRegistry; - - @BeforeEach - public void before() { - Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove); - simpleMeterRegistry = new SimpleMeterRegistry(); - Metrics.addRegistry(simpleMeterRegistry); - taskMetrics = new TaskMetrics(); - } - - @AfterEach - public void after() { - Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove); - } - - @Test - public void successfulTask() { - - TaskExecution taskExecution = new TaskExecution(123L, 0, "myTask72", new Date(), - new Date(), null, new ArrayList<>(), null, null, -1L); - - // Start Task - taskMetrics.onTaskStartup(taskExecution); - - //// Test Long Task Timer while the task is running. - LongTaskTimer longTaskTimer = simpleMeterRegistry - .find(TaskMetrics.SPRING_CLOUD_TASK_ACTIVE_METER).longTaskTimer(); - assertThat(longTaskTimer) - .withFailMessage("LongTask timer should be created on Task start") - .isNotNull(); - // assertThat(longTaskTimer.activeTasks()).isEqualTo(1); - assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) - .isEqualTo("myTask72"); - assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) - .isEqualTo("123"); - - // Finish Task - taskMetrics.onTaskEnd(taskExecution); - - // Test Timer - Timer taskTimer = simpleMeterRegistry.find(TaskMetrics.SPRING_CLOUD_TASK_METER) - .timer(); - assertThat(taskTimer).isNotNull(); - // assertThat(taskTimer.count()).isEqualTo(1L); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) - .isEqualTo("myTask72"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) - .isEqualTo("123"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXTERNAL_EXECUTION_ID_TAG)) - .isEqualTo("unknown"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_PARENT_EXECUTION_ID_TAG)) - .isEqualTo("-1"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXIT_CODE_TAG)) - .isEqualTo("0"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXCEPTION_TAG)) - .isEqualTo("none"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_STATUS_TAG)) - .isEqualTo(TaskMetrics.STATUS_SUCCESS); - - // Test Long Task Timer after the task has completed. - // LongTaskTimer longTaskTimer = simpleMeterRegistry - // .find(TaskMetrics.SPRING_CLOUD_TASK_ACTIVE_METER).longTaskTimer(); - assertThat(longTaskTimer.activeTasks()).isEqualTo(0); - assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) - .isEqualTo("myTask72"); - assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) - .isEqualTo("123"); - } - - @Test - public void failingTask() { - - TaskExecution taskExecution = new TaskExecution(123L, 0, "myTask", new Date(), - new Date(), null, new ArrayList<>(), null, null, -1L); - - // Start Task - taskMetrics.onTaskStartup(taskExecution); - - // Test Long Task Timer while the task is running. - LongTaskTimer longTaskTimer = simpleMeterRegistry - .find(TaskMetrics.SPRING_CLOUD_TASK_ACTIVE_METER).longTaskTimer(); - assertThat(longTaskTimer) - .withFailMessage("LongTask timer should be created on Task start") - .isNotNull(); - assertThat(longTaskTimer.activeTasks()).isEqualTo(1); - assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) - .isEqualTo("myTask"); - assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) - .isEqualTo("123"); - - taskMetrics.onTaskFailed(new RuntimeException("Test")); - - // Finish Task. TaskLifecycleListen calls onTaskEnd after the onTaskFailed. Make - // sure that the counter status - // is not affected by this. - taskMetrics.onTaskEnd(taskExecution); - - Timer taskTimer = simpleMeterRegistry.find(TaskMetrics.SPRING_CLOUD_TASK_METER) - .timer(); - assertThat(taskTimer).isNotNull(); - - assertThat(taskTimer).isNotNull(); - // assertThat(taskTimer.count()).isEqualTo(1L); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) - .isEqualTo("myTask"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) - .isEqualTo("123"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXTERNAL_EXECUTION_ID_TAG)) - .isEqualTo("unknown"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_PARENT_EXECUTION_ID_TAG)) - .isEqualTo("-1"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXIT_CODE_TAG)) - .isEqualTo("0"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXCEPTION_TAG)) - .isEqualTo("RuntimeException"); - assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_STATUS_TAG)) - .isEqualTo(TaskMetrics.STATUS_FAILURE); - - // Test Long Task Timer after the task has completed. - assertThat(longTaskTimer.activeTasks()).isEqualTo(0); - assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG)) - .isEqualTo("myTask"); - assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG)) - .isEqualTo("123"); - } - -} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskObservationsTests.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskObservationsTests.java new file mode 100644 index 00000000..79baa513 --- /dev/null +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/micrometer/TaskObservationsTests.java @@ -0,0 +1,367 @@ +/* + * Copyright 2019-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.micrometer; + +import java.util.ArrayList; +import java.util.Date; + +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.observation.TimerObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.core.tck.MeterRegistryAssert; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationHandler; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.tck.ObservationRegistryAssert; +import io.micrometer.observation.tck.TestObservationRegistry; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.task.configuration.TaskObservationCloudKeyValues; +import org.springframework.cloud.task.listener.TaskExecutionObservation; +import org.springframework.cloud.task.listener.TaskObservations; +import org.springframework.cloud.task.repository.TaskExecution; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.task.listener.TaskObservations.UNKNOWN; + +/** + * @author Christian Tzolov + * @author Glenn Renfro + */ +public class TaskObservationsTests { + + public static final String PREFIX = "spring.cloud.task"; + + private TaskObservations taskObservations; + + private SimpleMeterRegistry simpleMeterRegistry; + + private ObservationRegistry observationRegistry; + + @BeforeEach + public void before() { + this.simpleMeterRegistry = new SimpleMeterRegistry(); + this.observationRegistry = TestObservationRegistry.create(); + ObservationHandler timerObservationHandler = new TimerObservationHandler(this.simpleMeterRegistry); + this.observationRegistry.observationConfig().observationHandler(timerObservationHandler); + this.taskObservations = new TaskObservations(this.observationRegistry, null, null); + } + + @AfterEach + public void after() { + this.simpleMeterRegistry.clear(); + ObservationRegistryAssert.assertThat(this.observationRegistry).doesNotHaveAnyRemainingCurrentObservation(); + } + + @Test + public void successfulTaskTest() { + + TaskExecution taskExecution = startupObservationForBasicTests("myTask72", 123L); + + LongTaskTimer longTaskTimer = initializeBasicTest("myTask72", "123"); + + // Finish Task + taskObservations.onTaskEnd(taskExecution); + + verifyDefaultKeyValues(); + TaskExecutionObservation.TASK_ACTIVE.getDefaultConvention(); + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags("spring.cloud.task", + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_STATUS.getKeyName(), TaskObservations.STATUS_SUCCESS)); + + verifyLongTaskTimerAfterStop(longTaskTimer, "myTask72", "123"); + } + + @Test + public void defaultTaskTest() { + + TaskExecution taskExecution = new TaskExecution(123L, 0, null, new Date(), + new Date(), null, new ArrayList<>(), null, null, null); + + // Start Task + taskObservations.onTaskStartup(taskExecution); + + LongTaskTimer longTaskTimer = initializeBasicTest(UNKNOWN, "123"); + + + // Finish Task + taskObservations.onTaskEnd(taskExecution); + + // Test Timer + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_NAME.getKeyName(), UNKNOWN)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_EXECUTION_ID.getKeyName(), "123")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_PARENT_EXECUTION_ID.getKeyName(), UNKNOWN)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_EXTERNAL_EXECUTION_ID.getKeyName(), UNKNOWN)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_EXIT_CODE.getKeyName(), "0")); + + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_STATUS.getKeyName(), TaskObservations.STATUS_SUCCESS)); + + verifyLongTaskTimerAfterStop(longTaskTimer, "unknown", "123"); + + } + + @Test + public void failingTask() { + + TaskExecution taskExecution = startupObservationForBasicTests("myTask72", 123L); + + LongTaskTimer longTaskTimer = initializeBasicTest("myTask72", "123"); + + taskObservations.onTaskFailed(new RuntimeException("Test")); + + // Finish Task. TaskLifecycleListen calls onTaskEnd after the onTaskFailed. Make + // sure that the counter status + // is not affected by this. + taskExecution.setExitCode(1); + taskObservations.onTaskEnd(taskExecution); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_NAME.getKeyName(), "myTask72")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_EXECUTION_ID.getKeyName(), "123")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_PARENT_EXECUTION_ID.getKeyName(), "-1")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_EXIT_CODE.getKeyName(), "1")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_STATUS.getKeyName(), TaskObservations.STATUS_FAILURE)); + + verifyLongTaskTimerAfterStop(longTaskTimer, "myTask72", "123"); + } + + @Test + public void taskWithCloudKeyValues() { + final String APPLICATION_ID = "123"; + final String APPLICATION_NAME = "APP123"; + final String SPACE_ID = "123"; + final String SPACE_NAME = "SPACE123"; + final String APPLICATION_VERSION = "APPV123"; + final String INSTANCE_INDEX = "55"; + final String ORGANIZATION_NAME = "ORG123"; + TaskObservationCloudKeyValues taskObservationCloudKeyValues = new TaskObservationCloudKeyValues(); + taskObservationCloudKeyValues.setApplicationId(APPLICATION_ID); + taskObservationCloudKeyValues.setApplicationName(APPLICATION_NAME); + taskObservationCloudKeyValues.setSpaceId(SPACE_ID); + taskObservationCloudKeyValues.setSpaceName(SPACE_NAME); + taskObservationCloudKeyValues.setApplicationVersion(APPLICATION_VERSION); + taskObservationCloudKeyValues.setInstanceIndex(INSTANCE_INDEX); + taskObservationCloudKeyValues.setOrganizationName(ORGANIZATION_NAME); + this.taskObservations = new TaskObservations(this.observationRegistry, taskObservationCloudKeyValues, null); + + TaskExecution taskExecution = startupObservationForBasicTests("myTask72", 123L); + + LongTaskTimer longTaskTimer = initializeBasicTest("myTask72", "123"); + + // Finish Task + taskObservations.onTaskEnd(taskExecution); + + verifyDefaultKeyValues(); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_ORG_NAME.getKeyName(), ORGANIZATION_NAME)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_SPACE_ID.getKeyName(), SPACE_ID)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_SPACE_NAME.getKeyName(), SPACE_NAME)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_APP_NAME.getKeyName(), APPLICATION_NAME)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_APP_ID.getKeyName(), APPLICATION_ID)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_APP_VERSION.getKeyName(), APPLICATION_VERSION)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_INSTANCE_INDEX.getKeyName(), INSTANCE_INDEX)); + + // Test Timer + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_NAME.getKeyName(), "myTask72")); + + verifyLongTaskTimerAfterStop(longTaskTimer, "myTask72", "123"); + } + + @Test + public void testCloudVariablesUninitialized() { + ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + CloudConfigurationForDefaultValues.class)); + applicationContextRunner.run((context) -> { + TaskObservationCloudKeyValues taskObservationCloudKeyValues = context + .getBean(TaskObservationCloudKeyValues.class); + + assertThat(taskObservationCloudKeyValues) + .as("taskObservationCloudKeyValues should not be null").isNotNull(); + + this.taskObservations = new TaskObservations(this.observationRegistry, taskObservationCloudKeyValues, null); + + TaskExecution taskExecution = startupObservationForBasicTests("myTask72", 123L); + + LongTaskTimer longTaskTimer = initializeBasicTest("myTask72", "123"); + + // Finish Task + taskObservations.onTaskEnd(taskExecution); + + verifyDefaultKeyValues(); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_ORG_NAME.getKeyName(), "default")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_SPACE_ID.getKeyName(), UNKNOWN)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_SPACE_NAME.getKeyName(), UNKNOWN)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_APP_NAME.getKeyName(), UNKNOWN)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_APP_ID.getKeyName(), UNKNOWN)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_APP_VERSION.getKeyName(), UNKNOWN)); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_CF_INSTANCE_INDEX.getKeyName(), "0")); + + // Test Timer + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_NAME.getKeyName(), "myTask72")); + + verifyLongTaskTimerAfterStop(longTaskTimer, "myTask72", "123"); + }); + } + + private TaskExecution startupObservationForBasicTests(String taskName, long taskExecutionId) { + TaskExecution taskExecution = new TaskExecution(taskExecutionId, 0, taskName, new Date(), + new Date(), null, new ArrayList<>(), null, "-1", -1L); + + // Start Task + taskObservations.onTaskStartup(taskExecution); + return taskExecution; + } + + private LongTaskTimer initializeBasicTest(String taskName, String executionId) { + // Test Long Task Timer while the task is running. + LongTaskTimer longTaskTimer = simpleMeterRegistry + .find(TaskExecutionObservation.TASK_ACTIVE.getPrefix() + ".active").longTaskTimer(); + System.out.println(simpleMeterRegistry.getMetersAsString()); + assertThat(longTaskTimer) + .withFailMessage("LongTask timer should be created on Task start") + .isNotNull(); + assertThat(longTaskTimer.activeTasks()).isEqualTo(1); + assertThat(longTaskTimer.getId().getTag(TaskExecutionObservation.TaskKeyValues.TASK_NAME.getKeyName())) + .isEqualTo(taskName); + assertThat(longTaskTimer.getId().getTag(TaskExecutionObservation.TaskKeyValues.TASK_EXECUTION_ID.getKeyName())) + .isEqualTo(executionId); + return longTaskTimer; + } + + private void verifyDefaultKeyValues() { + // Test Timer + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_NAME.getKeyName(), "myTask72")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_EXECUTION_ID.getKeyName(), "123")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_PARENT_EXECUTION_ID.getKeyName(), "-1")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_EXIT_CODE.getKeyName(), "0")); + + MeterRegistryAssert.assertThat(this.simpleMeterRegistry) + .hasTimerWithNameAndTags(PREFIX, + Tags.of(TaskExecutionObservation.TaskKeyValues.TASK_STATUS.getKeyName(), TaskObservations.STATUS_SUCCESS)); + } + + private void verifyLongTaskTimerAfterStop(LongTaskTimer longTaskTimer, String taskName, String executionId) { + // Test Long Task Timer after the task has completed. + assertThat(longTaskTimer.activeTasks()).isEqualTo(0); + assertThat(longTaskTimer.getId().getTag(TaskExecutionObservation.TaskKeyValues.TASK_NAME.getKeyName())) + .isEqualTo(taskName); + assertThat(longTaskTimer.getId().getTag(TaskExecutionObservation.TaskKeyValues.TASK_EXECUTION_ID.getKeyName())) + .isEqualTo(executionId); + } + + @Configuration + static class CloudConfigurationForDefaultValues { + @Bean + public TaskObservationCloudKeyValues taskObservationCloudKeyValues() { + return new TaskObservationCloudKeyValues(); + } + } +} diff --git a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java index 0d9f1fd7..0c5e5ee8 100644 --- a/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java +++ b/spring-cloud-task-core/src/test/java/org/springframework/cloud/task/util/TestDefaultConfiguration.java @@ -18,10 +18,13 @@ package org.springframework.cloud.task.util; import javax.sql.DataSource; +import io.micrometer.observation.ObservationRegistry; + import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.task.configuration.TaskObservationCloudKeyValues; import org.springframework.cloud.task.configuration.TaskProperties; import org.springframework.cloud.task.listener.TaskLifecycleListener; import org.springframework.cloud.task.listener.TaskListenerExecutorObjectFactory; @@ -82,10 +85,12 @@ public class TestDefaultConfiguration implements InitializingBean { } @Bean - public TaskLifecycleListener taskHandler(TaskExplorer taskExplorer) { + public TaskLifecycleListener taskHandler(TaskExplorer taskExplorer, + @Autowired(required = false) io.micrometer.core.instrument.MeterRegistry meterRegistry, @Autowired(required = false) ObservationRegistry observationRegistry) { + return new TaskLifecycleListener(taskRepository(), taskNameResolver(), this.applicationArguments, taskExplorer, this.taskProperties, - taskListenerExecutorObjectProvider(this.context)); + taskListenerExecutorObjectProvider(this.context), observationRegistry, new TaskObservationCloudKeyValues()); } @Override diff --git a/spring-cloud-task-samples/pom.xml b/spring-cloud-task-samples/pom.xml index 36375e8a..ca437c49 100644 --- a/spring-cloud-task-samples/pom.xml +++ b/spring-cloud-task-samples/pom.xml @@ -28,7 +28,7 @@ task-events batch-events jpa-sample - task-metrics + task-observations multiple-datasources single-step-batch-job diff --git a/spring-cloud-task-samples/task-metrics/src/main/resources/application.properties b/spring-cloud-task-samples/task-metrics/src/main/resources/application.properties deleted file mode 100644 index e8aac3b6..00000000 --- a/spring-cloud-task-samples/task-metrics/src/main/resources/application.properties +++ /dev/null @@ -1,6 +0,0 @@ -logging.level.org.springframework.cloud.task=debug -management.metrics.tags.service=task-metrics-application -management.metrics.tags.application=task-metrics-application-58 -spring.cloud.task.name=taskmetrics -spring.cloud.task.observation.enabled=true - diff --git a/spring-cloud-task-samples/task-metrics/.gitignore b/spring-cloud-task-samples/task-observations/.gitignore similarity index 100% rename from spring-cloud-task-samples/task-metrics/.gitignore rename to spring-cloud-task-samples/task-observations/.gitignore diff --git a/spring-cloud-task-samples/task-metrics/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-task-samples/task-observations/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from spring-cloud-task-samples/task-metrics/.mvn/wrapper/maven-wrapper.jar rename to spring-cloud-task-samples/task-observations/.mvn/wrapper/maven-wrapper.jar diff --git a/spring-cloud-task-samples/task-metrics/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-task-samples/task-observations/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from spring-cloud-task-samples/task-metrics/.mvn/wrapper/maven-wrapper.properties rename to spring-cloud-task-samples/task-observations/.mvn/wrapper/maven-wrapper.properties diff --git a/spring-cloud-task-samples/task-metrics/README.adoc b/spring-cloud-task-samples/task-observations/README.adoc similarity index 100% rename from spring-cloud-task-samples/task-metrics/README.adoc rename to spring-cloud-task-samples/task-observations/README.adoc diff --git a/spring-cloud-task-samples/task-metrics/mvnw b/spring-cloud-task-samples/task-observations/mvnw similarity index 100% rename from spring-cloud-task-samples/task-metrics/mvnw rename to spring-cloud-task-samples/task-observations/mvnw diff --git a/spring-cloud-task-samples/task-metrics/mvnw.cmd b/spring-cloud-task-samples/task-observations/mvnw.cmd similarity index 100% rename from spring-cloud-task-samples/task-metrics/mvnw.cmd rename to spring-cloud-task-samples/task-observations/mvnw.cmd diff --git a/spring-cloud-task-samples/task-metrics/pom.xml b/spring-cloud-task-samples/task-observations/pom.xml similarity index 95% rename from spring-cloud-task-samples/task-metrics/pom.xml rename to spring-cloud-task-samples/task-observations/pom.xml index 6ab33d15..5aeca82c 100644 --- a/spring-cloud-task-samples/task-metrics/pom.xml +++ b/spring-cloud-task-samples/task-observations/pom.xml @@ -4,10 +4,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 io.spring - task-metrics + task-observations 3.0.0-SNAPSHOT - task-metrics - task-metrics + task observation sample + Displays task observations as well as commandline and application runner observations 17 2022.0.0-SNAPSHOT diff --git a/spring-cloud-task-samples/task-metrics/src/main/java/io/spring/taskmetrics/MetricConfiguration.java b/spring-cloud-task-samples/task-observations/src/main/java/io/spring/taskobservations/ObservationConfiguration.java similarity index 92% rename from spring-cloud-task-samples/task-metrics/src/main/java/io/spring/taskmetrics/MetricConfiguration.java rename to spring-cloud-task-samples/task-observations/src/main/java/io/spring/taskobservations/ObservationConfiguration.java index 750af882..fe8e21a6 100644 --- a/spring-cloud-task-samples/task-metrics/src/main/java/io/spring/taskmetrics/MetricConfiguration.java +++ b/spring-cloud-task-samples/task-observations/src/main/java/io/spring/taskobservations/ObservationConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.taskmetrics; +package io.spring.taskobservations; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; @@ -22,7 +22,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration -public class MetricConfiguration { +public class ObservationConfiguration { @Bean public SimpleMeterRegistry meterRegistry() { return new SimpleMeterRegistry(); diff --git a/spring-cloud-task-samples/task-metrics/src/main/java/io/spring/taskmetrics/TaskMetricsApplication.java b/spring-cloud-task-samples/task-observations/src/main/java/io/spring/taskobservations/TaskObservationsApplication.java similarity index 74% rename from spring-cloud-task-samples/task-metrics/src/main/java/io/spring/taskmetrics/TaskMetricsApplication.java rename to spring-cloud-task-samples/task-observations/src/main/java/io/spring/taskobservations/TaskObservationsApplication.java index 90cd38ce..8da7ee60 100644 --- a/spring-cloud-task-samples/task-metrics/src/main/java/io/spring/taskmetrics/TaskMetricsApplication.java +++ b/spring-cloud-task-samples/task-observations/src/main/java/io/spring/taskobservations/TaskObservationsApplication.java @@ -14,14 +14,13 @@ * limitations under the License. */ -package io.spring.taskmetrics; +package io.spring.taskobservations; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; @@ -33,35 +32,25 @@ import org.springframework.context.annotation.Bean; @SpringBootApplication @EnableTask -public class TaskMetricsApplication { +public class TaskObservationsApplication { - private static final Log logger = LogFactory.getLog(TaskMetricsApplication.class); + private static final Log logger = LogFactory.getLog(TaskObservationsApplication.class); @Autowired public SimpleMeterRegistry simpleMeterRegistry; public static void main(String[] args) { - SpringApplication.run(TaskMetricsApplication.class, args); + SpringApplication.run(TaskObservationsApplication.class, args); } @Bean public ApplicationRunner applicationRunner() { - return new ApplicationRunner() { - @Override - public void run(ApplicationArguments args) throws Exception { - logger.info("Hello ApplicationRunner Metric's World"); - } - }; + return args -> logger.info("Hello ApplicationRunner Metric's World"); } @Bean public CommandLineRunner commandLineRunner() { - return new CommandLineRunner() { - @Override - public void run(String... args) throws Exception { - logger.info("Hello CommandLineRunner Metric's World"); - } - }; + return args -> logger.info("Hello CommandLineRunner Metric's World"); } /** diff --git a/spring-cloud-task-samples/task-observations/src/main/resources/application.properties b/spring-cloud-task-samples/task-observations/src/main/resources/application.properties new file mode 100644 index 00000000..678c8937 --- /dev/null +++ b/spring-cloud-task-samples/task-observations/src/main/resources/application.properties @@ -0,0 +1,6 @@ +logging.level.org.springframework.cloud.task=debug +management.metrics.tags.service=task-observations-application +management.metrics.tags.application=task-observations-application-58 +spring.cloud.task.name=taskmetrics +spring.cloud.task.observation.enabled=true + diff --git a/spring-cloud-task-samples/task-metrics/src/test/java/io/spring/taskmetrics/TaskMetricsApplicationTests.java b/spring-cloud-task-samples/task-observations/src/test/java/io/spring/taskobservations/TaskObservationsApplicationTests.java similarity index 71% rename from spring-cloud-task-samples/task-metrics/src/test/java/io/spring/taskmetrics/TaskMetricsApplicationTests.java rename to spring-cloud-task-samples/task-observations/src/test/java/io/spring/taskobservations/TaskObservationsApplicationTests.java index e746ead9..938f32fe 100644 --- a/spring-cloud-task-samples/task-metrics/src/test/java/io/spring/taskmetrics/TaskMetricsApplicationTests.java +++ b/spring-cloud-task-samples/task-observations/src/test/java/io/spring/taskobservations/TaskObservationsApplicationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.taskmetrics; +package io.spring.taskobservations; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -27,14 +27,17 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; @SpringBootTest @ExtendWith(OutputCaptureExtension.class) -class TaskMetricsApplicationTests { +class TaskObservationsApplicationTests { @Test void contextLoads(CapturedOutput output) { String result = output.getAll(); - assertThat(result).contains("spring.cloud.task(TIMER)[application=" + - "'task-metrics-application-58', service='task-metrics-application', " + - "task.exception='none', task.execution.id='"); + assertThat(result).contains("spring.cloud.task(TIMER)[application='task-observations-application-58', " + + "error='none', service='task-observations-application', " + + "spring.cloud.task.execution.id='1', spring.cloud.task.exit.code='0', " + + "spring.cloud.task.external.execution.id='unknown', spring.cloud.task.name='taskmetrics', " + + "spring.cloud.task.parent.execution.id='unknown', spring.cloud.task.status='success']; " + + "count=1.0, total_time="); } } diff --git a/spring-cloud-task-stream/pom.xml b/spring-cloud-task-stream/pom.xml index 1be77a3b..198db76e 100644 --- a/spring-cloud-task-stream/pom.xml +++ b/spring-cloud-task-stream/pom.xml @@ -48,6 +48,7 @@ org.springframework.cloud spring-cloud-task-core + org.springframework.boot spring-boot-starter-test