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
This commit is contained in:
Glenn Renfro
2022-03-03 07:58:29 -05:00
parent ceb504b66c
commit 2f0f78a2c8
38 changed files with 1032 additions and 826 deletions

View File

@@ -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]
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-influx</artifactId>
<scope>runtime</scope>
</dependency>

View File

@@ -13,9 +13,6 @@
<packaging>jar</packaging>
<name>Spring Cloud Task Core</name>
<description>Spring Cloud Task</description>
<properties>
<jmock-version>1.49</jmock-version>
</properties>
<dependencies>
<dependency>
@@ -123,19 +120,8 @@
<artifactId>micrometer-observation</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>${jmock-version}</version>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
@@ -144,8 +130,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-test</artifactId>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>

View File

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

View File

@@ -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.
*/

View File

@@ -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> applicationArguments) {
ConfigurableApplicationContext context, TaskRepository taskRepository,
TaskExplorer taskExplorer, TaskNameResolver taskNameResolver,
ObjectProvider<ApplicationArguments> 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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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";
}
}

View File

@@ -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<? extends Observation.ObservationConvention<? extends Observation.Context>> 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";
}
}
}
}

View File

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

View File

@@ -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<TaskExecutionObservationContext> {
@Override
default boolean supportsContext(Observation.Context context) {
return context instanceof TaskExecutionObservationContext;
}
}

View File

@@ -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<ApplicationEve
private final TaskListenerExecutorObjectFactory taskListenerExecutorObjectFactory;
private final TaskMetrics taskMetrics;
private final TaskObservations taskObservations;
@Autowired
private ConfigurableApplicationContext context;
@@ -96,6 +99,9 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
@Autowired(required = false)
private Collection<TaskExecutionListener> taskExecutionListenersFromContext;
@Autowired(required = false)
private Observation.ObservationConvention observationConvention;
private List<TaskExecutionListener> taskExecutionListeners;
private TaskExecution taskExecution;
@@ -132,7 +138,9 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
public TaskLifecycleListener(TaskRepository taskRepository,
TaskNameResolver taskNameResolver, ApplicationArguments applicationArguments,
TaskExplorer taskExplorer, TaskProperties taskProperties,
TaskListenerExecutorObjectFactory taskListenerExecutorObjectFactory) {
TaskListenerExecutorObjectFactory taskListenerExecutorObjectFactory,
@Autowired(required = false) ObservationRegistry observationRegistry,
TaskObservationCloudKeyValues taskObservationCloudKeyValues) {
Assert.notNull(taskRepository, "A taskRepository is required");
Assert.notNull(taskNameResolver, "A taskNameResolver is required");
Assert.notNull(taskExplorer, "A taskExplorer is required");
@@ -146,7 +154,8 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
this.taskExplorer = taskExplorer;
this.taskProperties = taskProperties;
this.taskListenerExecutorObjectFactory = taskListenerExecutorObjectFactory;
this.taskMetrics = new TaskMetrics();
observationRegistry = observationRegistry == null ? ObservationRegistry.NOOP : observationRegistry;
this.taskObservations = new TaskObservations(observationRegistry, taskObservationCloudKeyValues, observationConvention);
}
/**
@@ -315,7 +324,7 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
}
private TaskExecution invokeOnTaskStartup(TaskExecution taskExecution) {
this.taskMetrics.onTaskStartup(taskExecution);
this.taskObservations.onTaskStartup(taskExecution);
TaskExecution listenerTaskExecution = getTaskExecutionCopy(taskExecution);
List<TaskExecutionListener> startupListenerList = new ArrayList<>(
this.taskExecutionListeners);
@@ -338,7 +347,9 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
}
private TaskExecution invokeOnTaskEnd(TaskExecution taskExecution) {
this.taskMetrics.onTaskEnd(taskExecution);
if (this.taskObservations != null) {
this.taskObservations.onTaskEnd(taskExecution);
}
TaskExecution listenerTaskExecution = getTaskExecutionCopy(taskExecution);
if (this.taskExecutionListeners != null) {
try {
@@ -362,7 +373,9 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
private TaskExecution invokeOnTaskError(TaskExecution taskExecution,
Throwable throwable) {
this.taskMetrics.onTaskFailed(throwable);
if (this.taskObservations != null) {
this.taskObservations.onTaskFailed(throwable);
}
TaskExecution listenerTaskExecution = getTaskExecutionCopy(taskExecution);
if (this.taskExecutionListeners != null) {
try {

View File

@@ -1,139 +0,0 @@
/*
* 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.listener;
import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import org.springframework.cloud.task.repository.TaskExecution;
/**
* Utility class for publishing Spring Cloud Task specific metrics via Micrometer.
* Intended for internal use only.
*
* @author Christian Tzolov
* @since 2.2
*/
public class TaskMetrics {
/**
* Task timer measurements. Records information about task duration and status.
*/
public static final String SPRING_CLOUD_TASK_METER = "spring.cloud.task";
/**
* LongTask timer measurement. Records the run-time status of long-time lasting tasks.
*/
public static final String SPRING_CLOUD_TASK_ACTIVE_METER = "spring.cloud.task.active";
/**
* Successful task execution status indicator.
*/
public static final String STATUS_SUCCESS = "success";
/**
* Failing task execution status indicator.
*/
public static final String STATUS_FAILURE = "failure";
/**
* task name measurement tag.
*/
public static final String TASK_NAME_TAG = "task.name";
/**
* task execution id tag.
*/
public static final String TASK_EXECUTION_ID_TAG = "task.execution.id";
/**
* task parent execution id tag.
*/
public static final String TASK_PARENT_EXECUTION_ID_TAG = "task.parent.execution.id";
/**
* task external execution id tag.
*/
public static final String TASK_EXTERNAL_EXECUTION_ID_TAG = "task.external.execution.id";
/**
* task exit code tag.
*/
public static final String TASK_EXIT_CODE_TAG = "task.exit.code";
/**
* task status tag. Can be either STATUS_SUCCESS or STATUS_FAILURE
*/
public static final String TASK_STATUS_TAG = "task.status";
/**
* task exception tag. Contains the name of the exception class in case of error or
* none otherwise.
*/
public static final String TASK_EXCEPTION_TAG = "task.exception";
private Timer.Sample taskSample;
private LongTaskTimer.Sample longTaskSample;
private Throwable exception;
public void onTaskStartup(TaskExecution taskExecution) {
LongTaskTimer longTaskTimer = LongTaskTimer
.builder(SPRING_CLOUD_TASK_ACTIVE_METER).description("Long task duration")
.tags(commonTags(taskExecution)).register(Metrics.globalRegistry);
this.longTaskSample = longTaskTimer.start();
this.taskSample = Timer.start(Metrics.globalRegistry);
}
public void onTaskFailed(Throwable throwable) {
this.exception = throwable;
}
public void onTaskEnd(TaskExecution taskExecution) {
if (this.taskSample != null) {
this.taskSample.stop(Timer.builder(SPRING_CLOUD_TASK_METER)
.description("Task duration").tags(commonTags(taskExecution))
.tag(TASK_EXIT_CODE_TAG, "" + taskExecution.getExitCode())
.tag(TASK_EXCEPTION_TAG,
(this.exception == null) ? "none"
: this.exception.getClass().getSimpleName())
.tag(TASK_STATUS_TAG,
(this.exception == null) ? STATUS_SUCCESS : STATUS_FAILURE).register(Metrics.globalRegistry));
this.taskSample = null;
}
if (this.longTaskSample != null) {
this.longTaskSample.stop();
this.longTaskSample = null;
}
}
private Tags commonTags(TaskExecution taskExecution) {
return Tags.of(TASK_NAME_TAG, taskExecution.getTaskName())
.and(TASK_EXECUTION_ID_TAG, "" + taskExecution.getExecutionId())
.and(TASK_PARENT_EXECUTION_ID_TAG,
"" + taskExecution.getParentExecutionId())
.and(TASK_EXTERNAL_EXECUTION_ID_TAG,
(taskExecution.getExternalExecutionId() == null) ? "unknown"
: "" + taskExecution.getExternalExecutionId());
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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.listener;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.cloud.task.configuration.TaskObservationCloudKeyValues;
import org.springframework.cloud.task.repository.TaskExecution;
/**
* Utility class for publishing Spring Cloud Task specific Observations via Micrometer.
* Intended for internal use only.
*
* @author Christian Tzolov
* @author Glenn Renfro
* @since 2.2
*/
public class TaskObservations {
/**
* Successful task execution status indicator.
*/
public static final String STATUS_SUCCESS = "success";
/**
* Failing task execution status indicator.
*/
public static final String STATUS_FAILURE = "failure";
/**
* Default for when value is not present.
*/
public static final String UNKNOWN = "unknown";
private ObservationRegistry observationRegistry;
private Observation.ObservationConvention customObservationConvention;
public TaskObservations(ObservationRegistry observationRegistry, TaskObservationCloudKeyValues taskObservationCloudKeyValues,
Observation.ObservationConvention customObservationConvention) {
this.observationRegistry = observationRegistry;
this.taskObservationCloudKeyValues = taskObservationCloudKeyValues;
this.customObservationConvention = customObservationConvention;
}
private Observation.Scope scope;
private TaskExecutionObservationConvention observationsProvider = new DefaultTaskExecutionObservationConvention();
private TaskExecutionObservationContext taskObservationContext;
TaskObservationCloudKeyValues taskObservationCloudKeyValues;
public void onTaskStartup(TaskExecution taskExecution) {
this.taskObservationContext = new TaskExecutionObservationContext(taskExecution);
Observation observation = TaskExecutionObservation.TASK_ACTIVE.observation(this.customObservationConvention, new DefaultTaskExecutionObservationConvention(), this.taskObservationContext, this.observationRegistry)
.contextualName(String.valueOf(taskExecution.getExecutionId()))
.keyValuesProvider(this.observationsProvider)
.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_NAME.getKeyName(), getValueOrDefault(taskExecution.getTaskName()))
.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_EXECUTION_ID.getKeyName(), "" + taskExecution.getExecutionId())
.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_PARENT_EXECUTION_ID.getKeyName(),
(getValueOrDefault(taskExecution.getParentExecutionId())))
.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_EXTERNAL_EXECUTION_ID.getKeyName(),
(getValueOrDefault(taskExecution.getExternalExecutionId())));
if (taskObservationCloudKeyValues != null) {
observation.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_CF_ORG_NAME.getKeyName(),
this.taskObservationCloudKeyValues.getOrganizationName());
observation.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_CF_SPACE_ID.getKeyName(),
this.taskObservationCloudKeyValues.getSpaceId());
observation.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_CF_SPACE_NAME.getKeyName(),
this.taskObservationCloudKeyValues.getSpaceName());
observation.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_CF_APP_ID.getKeyName(),
this.taskObservationCloudKeyValues.getApplicationId());
observation.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_CF_APP_NAME.getKeyName(),
this.taskObservationCloudKeyValues.getApplicationName());
observation.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_CF_APP_VERSION.getKeyName(),
this.taskObservationCloudKeyValues.getApplicationVersion());
observation.lowCardinalityKeyValue(TaskExecutionObservation.TaskKeyValues.TASK_CF_INSTANCE_INDEX.getKeyName(),
this.taskObservationCloudKeyValues.getInstanceIndex());
}
observation.start();
this.scope = observation.openScope();
}
private String getValueOrDefault(Object value) {
return (value != null) ? value.toString() : UNKNOWN;
}
public void onTaskFailed(Throwable throwable) {
this.taskObservationContext.setStatus(STATUS_FAILURE);
this.scope.getCurrentObservation().error(throwable);
}
public void onTaskEnd(TaskExecution taskExecution) {
if (this.scope != null) {
this.taskObservationContext.getTaskExecution().setExitCode(taskExecution.getExitCode());
this.scope.close();
this.scope.getCurrentObservation().stop();
}
}
}

View File

@@ -1,6 +1,4 @@
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.observation.ObservationTaskAutoConfiguration

View File

@@ -1,134 +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.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import mockit.Mock;
import mockit.MockUp;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Christian Tzolov
* @author Soby Chacko
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = { AbstractMicrometerTest.AutoConfigurationApplication.class })
@DirtiesContext
public class AbstractMicrometerTest {
@Autowired
protected SimpleMeterRegistry simpleMeterRegistry;
@Autowired
protected ConfigurableApplicationContext context;
protected Meter meter;
@BeforeEach
public void before() {
Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove);
assertThat(simpleMeterRegistry).isNotNull();
meter = simpleMeterRegistry.find("spring.integration.handlers").meter();
assertThat(meter).isNotNull().withFailMessage(
"The spring.integration.handlers meter must be present in SpringBoot apps!");
}
@AfterEach
public void after() {
Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove);
}
@BeforeAll
public static void setup() throws IOException {
String serviceJson = StreamUtils.copyToString(new DefaultResourceLoader()
.getResource("classpath:/micrometer/pcf-scs-info.json").getInputStream(),
Charset.forName("UTF-8"));
mockVcapServicesFromString(serviceJson);
}
public static MockUp<?> mockVcapServicesFromString(String serviceJson) {
final Map<String, String> env = System.getenv();
return new MockUp<System>() {
@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<String, String> 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;
}
}
}

View File

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

View File

@@ -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();
}
}
}

View File

@@ -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");
}
}

View File

@@ -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<Observation.Context> 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();
}
}
}

View File

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

View File

@@ -28,7 +28,7 @@
<module>task-events</module>
<module>batch-events</module>
<module>jpa-sample</module>
<module>task-metrics</module>
<module>task-observations</module>
<module>multiple-datasources</module>
<module>single-step-batch-job</module>
</modules>

View File

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

View File

@@ -4,10 +4,10 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.spring</groupId>
<artifactId>task-metrics</artifactId>
<artifactId>task-observations</artifactId>
<version>3.0.0-SNAPSHOT</version>
<name>task-metrics</name>
<description>task-metrics</description>
<name>task observation sample</name>
<description>Displays task observations as well as commandline and application runner observations</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.0-SNAPSHOT</spring-cloud.version>

View File

@@ -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();

View File

@@ -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");
}
/**

View File

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

View File

@@ -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=");
}
}

View File

@@ -48,6 +48,7 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-task-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>