diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegrationManagement.java b/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegrationManagement.java index a4f7a66216..746eab58d3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegrationManagement.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/EnableIntegrationManagement.java @@ -54,7 +54,7 @@ public @interface EnableIntegrationManagement { *
* Set this to false for disabling logging by default in all framework components that implement * {@link org.springframework.integration.support.management.IntegrationManagement} - * (channels, message handlers etc). It turns off logging such as "PreSend on channel", "Received message" etc. + * (channels, message handlers etc.). It turns off logging such as "PreSend on channel", "Received message" etc. *
* After the context is initialized, individual components can have their setting changed by invoking
* {@link org.springframework.integration.support.management.IntegrationManagement#setLoggingEnabled(boolean)}.
@@ -62,4 +62,15 @@ public @interface EnableIntegrationManagement {
*/
String defaultLoggingEnabled() default "true";
+ /**
+ * Set simple pattern component names matching for observation registry injection.
+ * @return simple pattern component names matching for observation registry injection.
+ * None by default - no unconditional observation instrumentation.
+ * Can be set to {@code *} to instrumentation all the integration components.
+ * The pattern can start with {@code !} to negate the matching.
+ * @since 6.0
+ * @see org.springframework.integration.support.utils.PatternMatchUtils#smartMatch(String, String...)
+ */
+ String[] observationPatterns() default { };
+
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfiguration.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfiguration.java
index 35f53b18c0..de071c96f3 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfiguration.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfiguration.java
@@ -75,7 +75,11 @@ public class IntegrationManagementConfiguration implements ImportAware, Environm
Boolean.parseBoolean(this.environment.resolvePlaceholders(
(String) this.attributes.get("defaultLoggingEnabled"))));
configurer.setMetricsCaptorProvider(metricsCaptorProvider);
- configurer.setObservationRegistry(observationRegistryProvider);
+ String[] observationPatterns = (String[]) this.attributes.get("observationPatterns");
+ if (observationPatterns.length > 0) {
+ configurer.setObservationPatterns(observationPatterns);
+ configurer.setObservationRegistry(observationRegistryProvider);
+ }
return configurer;
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfigurer.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfigurer.java
index 208e414b9f..bca7f98ae0 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfigurer.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationManagementConfigurer.java
@@ -19,6 +19,8 @@ package org.springframework.integration.config;
import java.util.HashSet;
import java.util.Set;
+import org.apache.commons.logging.Log;
+
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.ObjectProvider;
@@ -33,6 +35,7 @@ import org.springframework.integration.support.management.IntegrationManagement;
import org.springframework.integration.support.management.IntegrationManagement.ManagementOverrides;
import org.springframework.integration.support.management.metrics.MeterFacade;
import org.springframework.integration.support.management.metrics.MetricsCaptor;
+import org.springframework.integration.support.utils.PatternMatchUtils;
import org.springframework.lang.Nullable;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@@ -80,6 +83,8 @@ public class IntegrationManagementConfigurer
private ObjectProvider
* It has been found that in high-volume messaging environments, calls to methods such as
- * {@link org.apache.commons.logging.Log#isDebugEnabled()} can be quite expensive
+ * {@link Log#isDebugEnabled()} can be quite expensive
* and account for an inordinate amount of CPU time.
*
* Set this to 'false' to disable logging by default in all framework components that implement
@@ -135,6 +140,16 @@ public class IntegrationManagementConfigurer
this.observationRegistryProvider = observationRegistryProvider;
}
+ /**
+ * Set simple patterns for component names matching which has to be instrumented with a {@link ObservationRegistry}.
+ * @param observationPatterns the simple patterns to use.
+ * @since 6.0
+ * @see PatternMatchUtils#smartMatch(String, String...)
+ */
+ public void setObservationPatterns(String... observationPatterns) {
+ Assert.notEmpty(observationPatterns, "'observationPatterns' must not be empty");
+ this.observationPatterns = observationPatterns;
+ }
@Override
public void afterSingletonsInstantiated() {
@@ -171,19 +186,19 @@ public class IntegrationManagementConfigurer
private void registerComponentGauges() {
this.gauges.add(
this.metricsCaptor.gaugeBuilder("spring.integration.channels", this,
- (c) -> this.applicationContext.getBeansOfType(MessageChannel.class).size())
+ (c) -> this.applicationContext.getBeansOfType(MessageChannel.class).size())
.description("The number of message channels")
.build());
this.gauges.add(
this.metricsCaptor.gaugeBuilder("spring.integration.handlers", this,
- (c) -> this.applicationContext.getBeansOfType(MessageHandler.class).size())
+ (c) -> this.applicationContext.getBeansOfType(MessageHandler.class).size())
.description("The number of message handlers")
.build());
this.gauges.add(
this.metricsCaptor.gaugeBuilder("spring.integration.sources", this,
- (c) -> this.applicationContext.getBeansOfType(MessageSource.class).size())
+ (c) -> this.applicationContext.getBeansOfType(MessageSource.class).size())
.description("The number of message sources")
.build());
}
@@ -195,7 +210,10 @@ public class IntegrationManagementConfigurer
if (this.metricsCaptor != null) {
integrationManagement.registerMetricsCaptor(this.metricsCaptor);
}
- if (this.observationRegistry != null) {
+ if (this.observationRegistry != null &&
+ Boolean.TRUE.equals(PatternMatchUtils.smartMatch(
+ integrationManagement.getComponentName(), this.observationPatterns))) {
+
integrationManagement.registerObservationRegistry(this.observationRegistry);
}
}
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/core/MessagingTemplate.java b/spring-integration-core/src/main/java/org/springframework/integration/core/MessagingTemplate.java
index 28071502d3..c0180fafb8 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/core/MessagingTemplate.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/core/MessagingTemplate.java
@@ -21,6 +21,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.context.IntegrationProperties;
import org.springframework.integration.support.channel.ChannelResolverUtils;
+import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.core.GenericMessagingTemplate;
@@ -80,6 +81,7 @@ public class MessagingTemplate extends GenericMessagingTemplate {
}
@Override
+ @Nullable
public Message> sendAndReceive(MessageChannel destination, Message> requestMessage) {
if (!this.throwExceptionOnLateReplySet) {
synchronized (this) {
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageProducerSupport.java b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageProducerSupport.java
index cd937eed2a..6370c18f8c 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageProducerSupport.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/endpoint/MessageProducerSupport.java
@@ -31,7 +31,12 @@ import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.support.DefaultErrorMessageStrategy;
import org.springframework.integration.support.ErrorMessageStrategy;
import org.springframework.integration.support.ErrorMessageUtils;
+import org.springframework.integration.support.management.IntegrationInboundManagement;
import org.springframework.integration.support.management.TrackableComponent;
+import org.springframework.integration.support.management.observation.DefaultMessageReceiverObservationConvention;
+import org.springframework.integration.support.management.observation.IntegrationObservation;
+import org.springframework.integration.support.management.observation.MessageReceiverContext;
+import org.springframework.integration.support.management.observation.MessageReceiverObservationConvention;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -40,6 +45,7 @@ import org.springframework.messaging.support.ErrorMessage;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
+import io.micrometer.observation.ObservationRegistry;
import reactor.core.publisher.Flux;
/**
@@ -50,13 +56,19 @@ import reactor.core.publisher.Flux;
* @author Artem Bilan
* @author Gary Russell
*/
-public abstract class MessageProducerSupport extends AbstractEndpoint implements MessageProducer, TrackableComponent,
- SmartInitializingSingleton, IntegrationPattern {
+public abstract class MessageProducerSupport extends AbstractEndpoint
+ implements MessageProducer, TrackableComponent,
+ SmartInitializingSingleton, IntegrationPattern, IntegrationInboundManagement {
private final MessagingTemplate messagingTemplate = new MessagingTemplate();
private ErrorMessageStrategy errorMessageStrategy = new DefaultErrorMessageStrategy();
+ private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
+
+ @Nullable
+ private MessageReceiverObservationConvention observationConvention;
+
private MessageChannel outputChannel;
private String outputChannelName;
@@ -135,7 +147,7 @@ public abstract class MessageProducerSupport extends AbstractEndpoint implements
/**
* Configure the default timeout value to use for send operations.
* May be overridden for individual messages.
- * @param sendTimeout the send timeout in milliseconds
+ * @param sendTimeout the 'send' operation timeout in milliseconds
* @see MessagingTemplate#setSendTimeout
*/
public void setSendTimeout(long sendTimeout) {
@@ -172,6 +184,22 @@ public abstract class MessageProducerSupport extends AbstractEndpoint implements
return this.messagingTemplate;
}
+ /**
+ * Set a custom {@link MessageReceiverObservationConvention} for {@link IntegrationObservation#HANDLER}.
+ * Ignored if an {@link ObservationRegistry} is not configured for this component.
+ * @param observationConvention the {@link MessageReceiverObservationConvention} to use.
+ * @since 6.0
+ */
+ public void setObservationConvention(@Nullable MessageReceiverObservationConvention observationConvention) {
+ this.observationConvention = observationConvention;
+ }
+
+ @Override
+ public void registerObservationRegistry(ObservationRegistry observationRegistry) {
+ Assert.notNull(observationRegistry, "'observationRegistry' must not be null");
+ this.observationRegistry = observationRegistry;
+ }
+
@Override
public IntegrationPatternType getIntegrationPatternType() {
return IntegrationPatternType.inbound_channel_adapter;
@@ -216,14 +244,18 @@ public abstract class MessageProducerSupport extends AbstractEndpoint implements
}
}
- protected void sendMessage(Message> messageArg) {
- Message> message = messageArg;
+ protected void sendMessage(Message> message) {
if (message == null) {
throw new MessagingException("cannot send a null message");
}
- message = trackMessageIfAny(message);
+
try {
- this.messagingTemplate.send(getRequiredOutputChannel(), message);
+ IntegrationObservation.HANDLER.observation(
+ this.observationConvention,
+ DefaultMessageReceiverObservationConvention.INSTANCE,
+ () -> new MessageReceiverContext(message, getComponentName()),
+ this.observationRegistry)
+ .observe(() -> this.messagingTemplate.send(getRequiredOutputChannel(), trackMessageIfAny(message)));
}
catch (RuntimeException ex) {
if (!sendErrorMessageIfNecessary(message, ex)) {
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java
index a6c770f3a9..333e31d686 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/MessagingGatewaySupport.java
@@ -53,6 +53,10 @@ import org.springframework.integration.support.management.metrics.MeterFacade;
import org.springframework.integration.support.management.metrics.MetricsCaptor;
import org.springframework.integration.support.management.metrics.SampleFacade;
import org.springframework.integration.support.management.metrics.TimerFacade;
+import org.springframework.integration.support.management.observation.DefaultMessageRequestReplyReceiverObservationConvention;
+import org.springframework.integration.support.management.observation.IntegrationObservation;
+import org.springframework.integration.support.management.observation.MessageRequestReplyReceiverContext;
+import org.springframework.integration.support.management.observation.MessageRequestReplyReceiverObservationConvention;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -62,10 +66,12 @@ import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
+import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;
+import io.micrometer.observation.ObservationRegistry;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
@@ -87,7 +93,7 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
private static final long DEFAULT_TIMEOUT = 1000L;
- protected final MessagingTemplate messagingTemplate; // NOSONAR
+ protected final ConvertingMessagingTemplate messagingTemplate; // NOSONAR
private final SimpleMessageConverter messageConverter = new SimpleMessageConverter();
@@ -130,6 +136,11 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
private TimerFacade successTimer;
+ private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
+
+ @Nullable
+ private MessageRequestReplyReceiverObservationConvention observationConvention;
+
private volatile AbstractEndpoint replyMessageCorrelator;
private volatile boolean initialized;
@@ -152,7 +163,7 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
* @see #setErrorOnTimeout
*/
public MessagingGatewaySupport(boolean errorOnTimeout) {
- MessagingTemplate template = new MessagingTemplate();
+ ConvertingMessagingTemplate template = new ConvertingMessagingTemplate();
template.setMessageConverter(this.messageConverter);
template.setSendTimeout(DEFAULT_TIMEOUT);
template.setReceiveTimeout(this.replyTimeout);
@@ -354,6 +365,18 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
this.metricsCaptor = metricsCaptorToRegister;
}
+ @Override
+ public void registerObservationRegistry(ObservationRegistry observationRegistry) {
+ Assert.notNull(observationRegistry, "'observationRegistry' must not be null");
+ this.observationRegistry = observationRegistry;
+ }
+
+ public void setObservationConvention(
+ @Nullable MessageRequestReplyReceiverObservationConvention observationConvention) {
+
+ this.observationConvention = observationConvention;
+ }
+
@Override
protected void onInit() {
Assert.state(!(this.requestChannelName != null && this.requestChannel != null),
@@ -494,16 +517,16 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
@Nullable
protected Object sendAndReceive(Object object) {
- return doSendAndReceive(object, true);
+ return sendAndReceive(object, true);
}
@Nullable
protected Message> sendAndReceiveMessage(Object object) {
- return (Message>) doSendAndReceive(object, false);
+ return (Message>) sendAndReceive(object, false);
}
- @Nullable // NOSONAR
- private Object doSendAndReceive(Object object, boolean shouldConvert) {
+ @Nullable
+ private Object sendAndReceive(Object object, boolean shouldConvert) {
initializeIfNecessary();
Assert.notNull(object, "request must not be null");
MessageChannel channel = getRequestChannel();
@@ -515,36 +538,28 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
Object reply;
Message> requestMessage = null;
- SampleFacade sample = null;
try {
- if (this.metricsCaptor != null) {
- sample = this.metricsCaptor.start();
+ requestMessage = convertToRequestMessage(object, shouldConvert);
+ Message> replyMessage;
+
+ if (this.observationRegistry != ObservationRegistry.NOOP) {
+ replyMessage = sendAndReceiveWithObservation(channel, object, requestMessage);
}
- if (shouldConvert) {
- reply = this.messagingTemplate.convertSendAndReceive(channel, object, Object.class,
- this.historyWritingPostProcessor);
+ else if (this.metricsCaptor != null) {
+ replyMessage = sendAndReceiveWithMetrics(channel, object, requestMessage);
}
else {
- requestMessage = (object instanceof Message>)
- ? (Message>) object : this.requestMapper.toMessage(object);
- Assert.state(requestMessage != null, () -> "request mapper resulted in no message for " + object);
- requestMessage = this.historyWritingPostProcessor.postProcessMessage(requestMessage);
- reply = this.messagingTemplate.sendAndReceive(channel, requestMessage);
+ replyMessage = doSendAndReceive(channel, object, requestMessage);
}
- if (reply == null && this.errorOnTimeout) {
- throwMessageTimeoutException(object, "No reply received within timeout");
- }
- if (sample != null) {
- sample.stop(sendTimer());
+ reply = replyMessage;
+ if (shouldConvert) {
+ reply = this.messagingTemplate.getMessageConverter().fromMessage(replyMessage, Object.class);
}
}
catch (Throwable ex) { // NOSONAR (catch throwable)
logger.debug(() -> "failure occurred in gateway sendAndReceive: " + ex.getMessage());
reply = ex;
- if (sample != null) {
- sample.stop(buildSendTimer(false, ex.getClass().getSimpleName()));
- }
}
if (reply instanceof Throwable || reply instanceof ErrorMessage) {
@@ -557,6 +572,57 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
return reply;
}
+ private Message> convertToRequestMessage(Object object, boolean shouldConvert) {
+ if (shouldConvert) {
+ return this.messagingTemplate.doConvert(object, null, this.historyWritingPostProcessor);
+ }
+ else {
+ Message> requestMessage = (object instanceof Message>)
+ ? (Message>) object : this.requestMapper.toMessage(object);
+ Assert.state(requestMessage != null, () -> "request mapper resulted in no message for " + object);
+ return this.historyWritingPostProcessor.postProcessMessage(requestMessage);
+ }
+ }
+
+ private Message> sendAndReceiveWithObservation(MessageChannel requestChannel, Object object,
+ Message> requestMessage) {
+
+ MessageRequestReplyReceiverContext context =
+ new MessageRequestReplyReceiverContext(requestMessage, getComponentName());
+
+ return IntegrationObservation.GATEWAY.observation(this.observationConvention,
+ DefaultMessageRequestReplyReceiverObservationConvention.INSTANCE,
+ () -> context, this.observationRegistry)
+ .observe(() -> {
+ Message> replyMessage = doSendAndReceive(requestChannel, object, requestMessage);
+ context.setResponse(replyMessage);
+ return replyMessage;
+ });
+ }
+
+ private Message> sendAndReceiveWithMetrics(MessageChannel requestChannel, Object object,
+ Message> requestMessage) {
+
+ SampleFacade sample = this.metricsCaptor.start();
+ try {
+ Message> replyMessage = doSendAndReceive(requestChannel, object, requestMessage);
+ sample.stop(sendTimer());
+ return replyMessage;
+ }
+ catch (Throwable ex) {
+ sample.stop(buildSendTimer(false, ex.getClass().getSimpleName()));
+ throw ex;
+ }
+ }
+
+ private Message> doSendAndReceive(MessageChannel requestChannel, Object object, Message> requestMessage) {
+ Message> replyMessage = this.messagingTemplate.sendAndReceive(requestChannel, requestMessage);
+ if (replyMessage == null && this.errorOnTimeout) {
+ throwMessageTimeoutException(object, "No reply received within timeout");
+ }
+ return replyMessage;
+ }
+
@Nullable
private Object handleSendAndReceiveError(Object object, @Nullable Message> requestMessage, Throwable error,
boolean shouldConvert) {
@@ -646,23 +712,23 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
}
return Mono.defer(() -> {
- Object originalReplyChannelHeader = requestMessage.getHeaders().getReplyChannel();
- Object originalErrorChannelHeader = requestMessage.getHeaders().getErrorChannel();
+ Object originalReplyChannelHeader = requestMessage.getHeaders().getReplyChannel();
+ Object originalErrorChannelHeader = requestMessage.getHeaders().getErrorChannel();
- MonoReplyChannel replyChan = new MonoReplyChannel();
+ MonoReplyChannel replyChan = new MonoReplyChannel();
- Message> messageToSend = MutableMessageBuilder.fromMessage(requestMessage)
- .setReplyChannel(replyChan)
- .setHeader(this.messagingTemplate.getSendTimeoutHeader(), null)
- .setHeader(this.messagingTemplate.getReceiveTimeoutHeader(), null)
- .setErrorChannel(replyChan)
- .build();
+ Message> messageToSend = MutableMessageBuilder.fromMessage(requestMessage)
+ .setReplyChannel(replyChan)
+ .setHeader(this.messagingTemplate.getSendTimeoutHeader(), null)
+ .setHeader(this.messagingTemplate.getReceiveTimeoutHeader(), null)
+ .setErrorChannel(replyChan)
+ .build();
- sendMessageForReactiveFlow(requestChannel, messageToSend);
+ sendMessageForReactiveFlow(requestChannel, messageToSend);
- return buildReplyMono(requestMessage, replyChan.replyMono.asMono(), error, originalReplyChannelHeader,
- originalErrorChannelHeader);
- })
+ return buildReplyMono(requestMessage, replyChan.replyMono.asMono(), error, originalReplyChannelHeader,
+ originalErrorChannelHeader);
+ })
.onErrorResume(t -> error ? Mono.error(t) : handleSendError(requestMessage, t));
}
@@ -916,4 +982,19 @@ public abstract class MessagingGatewaySupport extends AbstractEndpoint
}
+ /**
+ * The {@link MessagingTemplate} extension to increase {@link #doConvert(Object, Map, MessagePostProcessor)}
+ * visibility to get access to the request message from an observation context.
+ */
+ protected static class ConvertingMessagingTemplate extends MessagingTemplate {
+
+ @Override // NOSONAR Increase visibility
+ public Message> doConvert(Object payload, @Nullable Map
+ * NOTE: This class is mostly intended for observation docs generation, so any string literals,
+ * even if they are the same, cannot be extracted into constants or super methods.
*
* @author Artem Bilan
*
@@ -47,6 +50,27 @@ public enum IntegrationObservation implements ObservationDocumentation {
return HandlerTags.values();
}
+ },
+
+ /**
+ * Observation for inbound message gateways.
+ */
+ GATEWAY {
+ @Override
+ public String getPrefix() {
+ return "spring.integration.";
+ }
+
+ @Override
+ public Class