Configure ObservationRegistry on JmsListener

Prior to this commit, we set in gh-37388 the ObservationRegistry on the
auto-configured JmsTemplate bean. This enables observations and context
propagation when sending JMS messages.

This commit applies the same to the `DefaultJmsListenerContainerFactory`
and the `DefaultJmsListenerContainerFactoryConfigurer`, in order to
enable observations on `@JmsListener` annotated methods.

This commit also refactors the support implemented in gh-37388 to avoid
relying on a bean post processor and instead set the observation
registry directly in the main auto-configuration: while Micrometer core
is an actuator-only dependency, Micrometer Observation API is a compile
dependnecy for spring-jms itself and there is no need to separate
concerns there.

Fixes gh-38613
This commit is contained in:
Brian Clozel 2023-12-01 09:36:00 +01:00
parent d172b22064
commit 0321a8a05b
10 changed files with 54 additions and 169 deletions

View File

@ -1,72 +0,0 @@
/*
* Copyright 2012-2023 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.boot.actuate.autoconfigure.observation.jms;
import io.micrometer.jakarta9.instrument.jms.JmsPublishObservationContext;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.Message;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsTemplate;
/**
* {@link EnableAutoConfiguration Auto-configuration} for instrumenting
* {@link JmsTemplate} beans for Observability.
*
* @author Brian Clozel
* @since 3.2.0
*/
@AutoConfiguration(after = { JmsAutoConfiguration.class, ObservationAutoConfiguration.class })
@ConditionalOnBean({ ObservationRegistry.class, JmsTemplate.class })
@ConditionalOnClass({ Observation.class, Message.class, JmsTemplate.class, JmsPublishObservationContext.class })
public class JmsTemplateObservationAutoConfiguration {
@Bean
static JmsTemplateObservationPostProcessor jmsTemplateObservationPostProcessor(
ObjectProvider<ObservationRegistry> observationRegistry) {
return new JmsTemplateObservationPostProcessor(observationRegistry);
}
static class JmsTemplateObservationPostProcessor implements BeanPostProcessor {
private final ObjectProvider<ObservationRegistry> observationRegistry;
JmsTemplateObservationPostProcessor(ObjectProvider<ObservationRegistry> observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof JmsTemplate jmsTemplate) {
this.observationRegistry.ifAvailable(jmsTemplate::setObservationRegistry);
}
return bean;
}
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2012-2023 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.
*/
/**
* Auto-configuration for JMS observations.
*/
package org.springframework.boot.actuate.autoconfigure.observation.jms;

View File

@ -71,7 +71,6 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.statsd.StatsdMetri
org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontMetricsExportAutoConfiguration
org.springframework.boot.actuate.autoconfigure.observation.batch.BatchObservationAutoConfiguration
org.springframework.boot.actuate.autoconfigure.observation.graphql.GraphQlObservationAutoConfiguration
org.springframework.boot.actuate.autoconfigure.observation.jms.JmsTemplateObservationAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.integration.IntegrationMetricsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.jersey.JerseyServerMetricsAutoConfiguration

View File

@ -1,70 +0,0 @@
/*
* Copyright 2012-2023 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.boot.actuate.autoconfigure.observation.jms;
import jakarta.jms.ConnectionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link JmsTemplateObservationAutoConfiguration}.
*
* @author Brian Clozel
*/
class JmsTemplateObservationAutoConfigurationTests {
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JmsAutoConfiguration.class, ObservationAutoConfiguration.class,
JmsTemplateObservationAutoConfiguration.class))
.withUserConfiguration(JmsConnectionConfiguration.class);
@Test
void shouldConfigureObservationRegistryOnTemplate() {
this.contextRunner.run((context) -> {
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
assertThat(jmsTemplate).extracting("observationRegistry").isNotNull();
});
}
@Test
void shouldBackOffWhenMicrometerJakartaIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader("io.micrometer.jakarta")).run((context) -> {
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
assertThat(jmsTemplate).extracting("observationRegistry").isNull();
});
}
static class JmsConnectionConfiguration {
@Bean
ConnectionFactory connectionFactory() {
return mock(ConnectionFactory.class);
}
}
}

View File

@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.jms;
import java.time.Duration;
import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.ConnectionFactory;
import jakarta.jms.ExceptionListener;
@ -49,6 +50,8 @@ public final class DefaultJmsListenerContainerFactoryConfigurer {
private JmsProperties jmsProperties;
private ObservationRegistry observationRegistry;
/**
* Set the {@link DestinationResolver} to use or {@code null} if no destination
* resolver should be associated with the factory by default.
@ -93,6 +96,15 @@ public final class DefaultJmsListenerContainerFactoryConfigurer {
this.jmsProperties = jmsProperties;
}
/**
* Set the {@link ObservationRegistry} to use.
* @param observationRegistry the {@link ObservationRegistry}
* @since 3.2.1
*/
public void setObservationRegistry(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
/**
* Configure the specified jms listener container factory. The factory can be further
* tuned and default settings can be overridden.
@ -115,6 +127,7 @@ public final class DefaultJmsListenerContainerFactoryConfigurer {
if (this.transactionManager == null && sessionProperties.getTransacted() == null) {
factory.setSessionTransacted(true);
}
map.from(this.observationRegistry).to(factory::setObservationRegistry);
map.from(sessionProperties::getTransacted).to(factory::setSessionTransacted);
map.from(listenerProperties::isAutoStartup).to(factory::setAutoStartup);
map.from(listenerProperties::formatConcurrency).to(factory::setConcurrency);

View File

@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.jms;
import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.ConnectionFactory;
import jakarta.jms.ExceptionListener;
@ -53,15 +54,19 @@ class JmsAnnotationDrivenConfiguration {
private final ObjectProvider<ExceptionListener> exceptionListener;
private final ObjectProvider<ObservationRegistry> observationRegistry;
private final JmsProperties properties;
JmsAnnotationDrivenConfiguration(ObjectProvider<DestinationResolver> destinationResolver,
ObjectProvider<JtaTransactionManager> transactionManager, ObjectProvider<MessageConverter> messageConverter,
ObjectProvider<ExceptionListener> exceptionListener, JmsProperties properties) {
ObjectProvider<ExceptionListener> exceptionListener,
ObjectProvider<ObservationRegistry> observationRegistry, JmsProperties properties) {
this.destinationResolver = destinationResolver;
this.transactionManager = transactionManager;
this.messageConverter = messageConverter;
this.exceptionListener = exceptionListener;
this.observationRegistry = observationRegistry;
this.properties = properties;
}
@ -73,6 +78,7 @@ class JmsAnnotationDrivenConfiguration {
configurer.setTransactionManager(this.transactionManager.getIfUnique());
configurer.setMessageConverter(this.messageConverter.getIfUnique());
configurer.setExceptionListener(this.exceptionListener.getIfUnique());
configurer.setObservationRegistry(this.observationRegistry.getIfUnique());
configurer.setJmsProperties(this.properties);
return configurer;
}

View File

@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.jms;
import java.time.Duration;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.ConnectionFactory;
import jakarta.jms.Message;
@ -74,12 +75,16 @@ public class JmsAutoConfiguration {
private final ObjectProvider<MessageConverter> messageConverter;
private final ObjectProvider<ObservationRegistry> observationRegistry;
public JmsTemplateConfiguration(JmsProperties properties,
ObjectProvider<DestinationResolver> destinationResolver,
ObjectProvider<MessageConverter> messageConverter) {
ObjectProvider<MessageConverter> messageConverter,
ObjectProvider<ObservationRegistry> observationRegistry) {
this.properties = properties;
this.destinationResolver = destinationResolver;
this.messageConverter = messageConverter;
this.observationRegistry = observationRegistry;
}
@Bean
@ -91,6 +96,7 @@ public class JmsAutoConfiguration {
template.setPubSubDomain(this.properties.isPubSubDomain());
map.from(this.destinationResolver::getIfUnique).whenNonNull().to(template::setDestinationResolver);
map.from(this.messageConverter::getIfUnique).whenNonNull().to(template::setMessageConverter);
map.from(this.observationRegistry::getIfUnique).whenNonNull().to(template::setObservationRegistry);
mapTemplateProperties(this.properties.getTemplate(), template);
return template;
}

View File

@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.jms;
import java.io.IOException;
import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.ConnectionFactory;
import jakarta.jms.ExceptionListener;
import jakarta.jms.Session;
@ -258,6 +259,17 @@ class JmsAutoConfigurationTests {
});
}
@Test
void testDefaultContainerFactoryWithObservationRegistry() {
ObservationRegistry observationRegistry = mock(ObservationRegistry.class);
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
.withBean(ObservationRegistry.class, () -> observationRegistry)
.run((context) -> {
DefaultMessageListenerContainer container = getContainer(context, "jmsListenerContainerFactory");
assertThat(container.getObservationRegistry()).isSameAs(observationRegistry);
});
}
@Test
void testCustomContainerFactoryWithConfigurer() {
this.contextRunner.withUserConfiguration(TestConfiguration9.class, EnableJmsConfiguration.class)
@ -290,6 +302,17 @@ class JmsAutoConfigurationTests {
.isSameAs(context.getBean("myDestinationResolver")));
}
@Test
void testJmsTemplateWithObservationRegistry() {
ObservationRegistry observationRegistry = mock(ObservationRegistry.class);
this.contextRunner.withUserConfiguration(EnableJmsConfiguration.class)
.withBean(ObservationRegistry.class, () -> observationRegistry)
.run((context) -> {
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
assertThat(jmsTemplate).extracting("observationRegistry").isSameAs(observationRegistry);
});
}
@Test
void testJmsTemplateFullCustomization() {
this.contextRunner.withUserConfiguration(MessageConvertersConfiguration.class)

View File

@ -745,10 +745,9 @@ Metrics are tagged by the name of the executor, which is derived from the bean n
[[actuator.metrics.supported.jms]]
==== JMS Metrics
Auto-configuration enables the instrumentation of all available `JmsTemplate` beans.
`JmsMessagingTemplate` instances built with instrumented `JmsTemplate` beans will also record observations.
See the {spring-framework-docs}/integration/observability.html#observability.jms.publish[Spring Framework reference documentation for more information on produced observations].
Auto-configuration enables the instrumentation of all available `JmsTemplate` beans and `@JmsListener` annotated methods.
This will produce `"jms.message.publish"` and `"jms.message.process"` metrics respectively.
See the {spring-framework-docs}/integration/observability.html#observability.jms[Spring Framework reference documentation for more information on produced observations].
[[actuator.metrics.supported.spring-mvc]]

View File

@ -1,6 +1,7 @@
<?xml version="1.0"?>
<!DOCTYPE import-control PUBLIC "-//Checkstyle//DTD ImportControl Configuration 1.4//EN" "https://checkstyle.org/dtds/import_control_1_4.dtd">
<import-control pkg="org.springframework.boot">
<allow pkg="io.micrometer.observation" />
<disallow pkg="io.micrometer" />
<disallow pkg="org.springframework.lang" />
<allow pkg=".*" regex="true" />