Support Observations for CommandLineRunner and ApplicationRunner

First pass on observation

Added automatic docs generation

Renamed Observed -> Observation

Fixed copyright dates

Removed execution task listener - it makes runner spans share the same trace id

Updated version for micrometer docs to milestone
This commit is contained in:
Marcin Grzejszczak
2022-05-11 11:22:01 +02:00
committed by Glenn Renfro
parent 79a9a59e08
commit 3817fe14e7
16 changed files with 699 additions and 1 deletions

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.configuration.observation;
import io.micrometer.common.KeyValues;
import io.micrometer.observation.Observation;
/**
* {@link Observation.KeyValuesProvider} for Spring Cloud Task.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
public class DefaultTaskKeyValuesProvider implements TaskKeyValuesProvider {
@Override
public KeyValues getLowCardinalityKeyValues(TaskObservationContext context) {
return KeyValues.of(TaskDocumentedObservation.TaskRunnerTags.BEAN_NAME.of(context.getBeanName()));
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.configuration.observation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
/**
* Observed representation of a {@link ApplicationRunner}.
*
* @author Marcin Grzejszczak
*/
class ObservationApplicationRunner implements ApplicationRunner, Observation.KeyValuesProviderAware<TaskKeyValuesProvider> {
private final BeanFactory beanFactory;
private final ApplicationRunner delegate;
private final String beanName;
private ObservationRegistry registry;
private TaskKeyValuesProvider keyValuesProvider = new DefaultTaskKeyValuesProvider();
ObservationApplicationRunner(BeanFactory beanFactory, ApplicationRunner delegate, String beanName) {
this.beanFactory = beanFactory;
this.delegate = delegate;
this.beanName = beanName;
}
@Override
public void run(ApplicationArguments args) throws Exception {
TaskObservationContext context = new TaskObservationContext(this.beanName);
Observation observation = TaskDocumentedObservation.TASK_RUNNER_OBSERVATION.observation(registry(), context)
.contextualName(this.beanName)
.keyValuesProvider(this.keyValuesProvider);
try (Observation.Scope scope = observation.start().openScope()) {
this.delegate.run(args);
}
catch (Exception error) {
observation.error(error);
throw error;
}
finally {
observation.stop();
}
}
private ObservationRegistry registry() {
if (this.registry == null) {
this.registry = this.beanFactory.getBean(ObservationRegistry.class);
}
return this.registry;
}
@Override
public void setKeyValuesProvider(TaskKeyValuesProvider keyValuesProvider) {
this.keyValuesProvider = keyValuesProvider;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.configuration.observation;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.ApplicationRunner;
/**
* Registers beans related to task scheduling.
*
* @author Marcin Grzejszczak
*/
class ObservationApplicationRunnerBeanPostProcessor implements BeanPostProcessor {
private final BeanFactory beanFactory;
ObservationApplicationRunnerBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ApplicationRunner applicationRunner && !(bean instanceof ObservationApplicationRunner)) {
return new ObservationApplicationRunner(this.beanFactory, applicationRunner, beanName);
}
return bean;
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.configuration.observation;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.CommandLineRunner;
/**
* Observed representation of a {@link CommandLineRunner}.
*
* @author Marcin Grzejszczak
*/
class ObservationCommandLineRunner implements CommandLineRunner, Observation.KeyValuesProviderAware<TaskKeyValuesProvider> {
private final BeanFactory beanFactory;
private final CommandLineRunner delegate;
private final String beanName;
private ObservationRegistry registry;
private TaskKeyValuesProvider keyValuesProvider = new DefaultTaskKeyValuesProvider();
ObservationCommandLineRunner(BeanFactory beanFactory, CommandLineRunner delegate, String beanName) {
this.beanFactory = beanFactory;
this.delegate = delegate;
this.beanName = beanName;
}
@Override
public void run(String... args) throws Exception {
TaskObservationContext context = new TaskObservationContext(this.beanName);
Observation observation = TaskDocumentedObservation.TASK_RUNNER_OBSERVATION.observation(registry(), context)
.contextualName(this.beanName)
.keyValuesProvider(this.keyValuesProvider);
try (Observation.Scope scope = observation.start().openScope()) {
this.delegate.run(args);
}
catch (Exception error) {
observation.error(error);
throw error;
}
finally {
observation.stop();
}
}
private ObservationRegistry registry() {
if (this.registry == null) {
this.registry = this.beanFactory.getBean(ObservationRegistry.class);
}
return this.registry;
}
@Override
public void setKeyValuesProvider(TaskKeyValuesProvider keyValuesProvider) {
this.keyValuesProvider = keyValuesProvider;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.configuration.observation;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.CommandLineRunner;
/**
* Registers beans related to task scheduling.
*
* @author Marcin Grzejszczak
*/
class ObservationCommandLineRunnerBeanPostProcessor implements BeanPostProcessor {
private final BeanFactory beanFactory;
ObservationCommandLineRunnerBeanPostProcessor(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof CommandLineRunner commandLineRunner && !(bean instanceof ObservationCommandLineRunner)) {
return new ObservationCommandLineRunner(this.beanFactory, commandLineRunner, beanName);
}
return bean;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.configuration.observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} that registers instrumentation for Spring Cloud Task.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObservationRegistry.class)
@ConditionalOnProperty(value = "spring.cloud.task.observation.enabled", matchIfMissing = true)
@ConditionalOnBean(ObservationRegistry.class)
@AutoConfigureAfter(ObservationAutoConfiguration.class)
public class ObservationTaskAutoConfiguration {
@Bean
static ObservationCommandLineRunnerBeanPostProcessor observedCommandLineRunnerBeanPostProcessor(BeanFactory beanFactory) {
return new ObservationCommandLineRunnerBeanPostProcessor(beanFactory);
}
@Bean
static ObservationApplicationRunnerBeanPostProcessor observedApplicationRunnerBeanPostProcessor(BeanFactory beanFactory) {
return new ObservationApplicationRunnerBeanPostProcessor(beanFactory);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.configuration.observation;
import io.micrometer.common.docs.KeyName;
import io.micrometer.observation.docs.DocumentedObservation;
enum TaskDocumentedObservation implements DocumentedObservation {
/**
* Observation created when a task runner is executed.
*/
TASK_RUNNER_OBSERVATION {
@Override
public String getName() {
return "spring.cloud.task.runner";
}
@Override
public KeyName[] getLowCardinalityKeyNames() {
return TaskRunnerTags.values();
}
@Override
public String getPrefix() {
return "spring.cloud.task";
}
};
/**
* Key names for Spring Cloud Task Command / Application runners.
*/
enum TaskRunnerTags implements KeyName {
/**
* Name of the bean that was executed by Spring Cloud Task.
*/
BEAN_NAME {
@Override
public String getKeyName() {
return "spring.cloud.task.runner.bean-name";
}
}
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.configuration.observation;
import io.micrometer.observation.Observation;
/**
* {@link Observation.KeyValuesProvider} for Spring Cloud Task.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
public interface TaskKeyValuesProvider extends Observation.KeyValuesProvider<TaskObservationContext> {
@Override
default boolean supportsContext(Observation.Context context) {
return context instanceof TaskObservationContext;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.configuration.observation;
import io.micrometer.observation.Observation;
/**
* {@link Observation.Context} for Spring Cloud Task.
*
* @author Marcin Grzejszczak
* @since 3.0.0
*/
public class TaskObservationContext extends Observation.Context {
private final String beanName;
public TaskObservationContext(String beanName) {
this.beanName = beanName;
}
public String getBeanName() {
return beanName;
}
}

View File

@@ -1,5 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.task.configuration.SingleTaskConfiguration,\
org.springframework.cloud.task.configuration.SimpleTaskAutoConfiguration,\
org.springframework.cloud.task.configuration.MetricsAutoConfiguration\
org.springframework.cloud.task.configuration.MetricsAutoConfiguration,\
org.springframework.cloud.task.configuration.observation.ObservationTaskAutoConfiguration