This commit is contained in:
Michael Minella
2019-07-03 11:01:08 -05:00
parent d4b51a074b
commit 0af72e31ac
9 changed files with 144 additions and 331 deletions

View File

@@ -1,79 +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 io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.task.metrics.fork.MeterRegistryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
/**
* Micrometer common tags for Cloud Foundry deployment properties. Based on the CF
* application environment variables:
* https://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html
*
* Tags are set only if the "cloud" Spring profile is set. The "cloud" profile is
* activated automatically when an application is deployed in CF:
* https://docs.cloudfoundry.org/buildpacks/java/configuring-service-connections/spring-service-bindings.html#cloud-profiles
*
* Use the spring.cloud.task.metrics.cf.tags.enabled=false property to disable inserting
* those tags.
*
* @author Christian Tzolov
*/
@Configuration
@Profile("cloud")
@ConditionalOnProperty(name = "spring.cloud.task.metrics.cf.tags.enabled",
havingValue = "true", matchIfMissing = true)
public class CloudFoundryMicrometerTagsConfiguration {
@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;
@Bean
public MeterRegistryCustomizer<MeterRegistry> cloudFoundryMetricsCommonTags() {
return registry -> 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);
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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.CompositeMeterRegistryAutoConfiguration" })
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

@@ -1,63 +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 io.micrometer.core.instrument.MeterRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.task.metrics.fork.MeterRegistryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Auto configuration extends the micrometer metrics with additional tags such as: task
* name, application name, instance index and guids. Later are necessary to allow
* discrimination and aggregation of app metrics by external metrics collection and
* visualizaiton tools.
*
* Use the spring.cloud.task.metrics.common.tags.enabled=false property to disable
* inserting those tags.
*
* @author Christian Tzolov
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.task.metrics.common.tags.enabled",
havingValue = "true", matchIfMissing = true)
public class SpringCloudTaskMicrometerCommonTagsConfiguration {
@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;
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("task.name", taskName)
.commonTags("task.execution.id", taskExecutionId)
.commonTags("task.external.execution.id", taskExternalExecutionId)
.commonTags("task.parent.execution.id", taskParentExecutionId);
}
}

View File

@@ -362,7 +362,7 @@ public class TaskLifecycleListener implements ApplicationListener<ApplicationEve
private TaskExecution invokeOnTaskError(TaskExecution taskExecution,
Throwable throwable) {
this.taskMetrics.onTaskFailed(taskExecution, throwable);
this.taskMetrics.onTaskFailed(throwable);
TaskExecution listenerTaskExecution = getTaskExecutionCopy(taskExecution);
if (this.taskExecutionListeners != null) {
try {

View File

@@ -24,7 +24,11 @@ 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 {
@@ -99,7 +103,7 @@ public class TaskMetrics {
this.taskSample = Timer.start(Metrics.globalRegistry);
}
public void onTaskFailed(TaskExecution taskExecution, Throwable throwable) {
public void onTaskFailed(Throwable throwable) {
this.exception = throwable;
}

View File

@@ -1,44 +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.metrics.fork;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
/**
* Forked from the
* org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer form
* spring-boot-actuator
*
* Callback interface that can be used to customize auto-configured {@link MeterRegistry
* MeterRegistries}.
* <p>
* Customizers are guaranteed to be applied before any {@link Meter} is registered with
* the registry.
*
* @author Michael Minella
*/
@FunctionalInterface
public interface MeterRegistryCustomizer<T extends MeterRegistry> {
/**
* Customize the given {@code registry}.
* @param registry the registry to customize
*/
void customize(T registry);
}

View File

@@ -1,139 +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.metrics.fork;
import java.util.List;
import java.util.stream.Collectors;
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.ObjectProvider;
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.boot.util.LambdaSafe;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Forked and stripped down 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
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Timed.class)
@AutoConfigureBefore(name = {
"org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration" })
public class MetricsAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Clock micrometerClockFork() {
return Clock.SYSTEM;
}
@Bean
public static MeterRegistryPostProcessor meterRegistryPostProcessorFork(
ObjectProvider<MeterRegistryCustomizer<?>> meterRegistryCustomizers) {
return new MeterRegistryPostProcessor(meterRegistryCustomizers);
}
/**
* Forked and stripped down version of the
* org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryPostProcessor
* form spring-boot-actuator
*
* {@link BeanPostProcessor} that delegates to a lazily created
* {@link MeterRegistryConfigurer} to post-process {@link MeterRegistry} beans.
*
*/
static class MeterRegistryPostProcessor implements BeanPostProcessor {
private final ObjectProvider<MeterRegistryCustomizer<?>> meterRegistryCustomizers;
private volatile MeterRegistryConfigurer configurer;
MeterRegistryPostProcessor(
ObjectProvider<MeterRegistryCustomizer<?>> meterRegistryCustomizers) {
this.meterRegistryCustomizers = meterRegistryCustomizers;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof MeterRegistry) {
getConfigurer().configure((MeterRegistry) bean);
}
return bean;
}
private MeterRegistryConfigurer getConfigurer() {
if (this.configurer == null) {
this.configurer = new MeterRegistryConfigurer(
this.meterRegistryCustomizers);
}
return this.configurer;
}
}
/**
* Forked and stripped down version of the
* org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryConfigurer form
* spring-boot-actuator
*
* Configurer to apply {@link MeterRegistryCustomizer customizers} to
* {@link MeterRegistry meter registries}.
*
*/
static class MeterRegistryConfigurer {
private final ObjectProvider<MeterRegistryCustomizer<?>> customizers;
MeterRegistryConfigurer(ObjectProvider<MeterRegistryCustomizer<?>> customizers) {
this.customizers = customizers;
}
void configure(MeterRegistry registry) {
customize(registry);
}
@SuppressWarnings("unchecked")
private void customize(MeterRegistry registry) {
LambdaSafe
.callbacks(MeterRegistryCustomizer.class,
asOrderedList(this.customizers), registry)
.withLogger(MeterRegistryConfigurer.class)
.invoke((customizer) -> customizer.customize(registry));
}
private <T> List<T> asOrderedList(ObjectProvider<T> provider) {
return provider.orderedStream().collect(Collectors.toList());
}
}
}

View File

@@ -1,7 +1,5 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.task.configuration.SingleTaskConfiguration,\
org.springframework.cloud.task.configuration.SimpleTaskAutoConfiguration,\
org.springframework.cloud.task.configuration.CloudFoundryMicrometerTagsConfiguration,\
org.springframework.cloud.task.configuration.SpringCloudTaskMicrometerCommonTagsConfiguration,\
org.springframework.cloud.task.metrics.fork.MetricsAutoConfiguration\
org.springframework.cloud.task.configuration.MetricsAutoConfiguration\

View File

@@ -129,7 +129,7 @@ public class TaskMetricsTests {
assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG))
.isEqualTo("123");
taskMetrics.onTaskFailed(taskExecution, new RuntimeException("Test"));
taskMetrics.onTaskFailed(new RuntimeException("Test"));
// Finish Task. TaskLifecycleListen calls onTaskEnd after the onTaskFailed. Make
// sure that the counter status