From ebe79aedfbaec7d6b92f5734902060e4f5bde00c Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 5 Jan 2021 15:08:30 -0500 Subject: [PATCH] GH-3452: Warn Publisher.onError in NullChannel (#3453) * GH-3452: Warn Publisher.onError in NullChannel Fixes https://github.com/spring-projects/spring-integration/issues/3452 The `NullChannel` can subscribe to the `Publisher` payload fully ignoring the possible data according the `NullChannel` nature. However an error thrown from the reactive stream processing is also ignored. * Log WARN message from the `Subscriber.onError()` when `NullChannel` subscribes to the `Publisher` produced to this channel. * Mention the logic in the `NullChannel` docs; point to the `ReactiveRequestHandlerAdvice` for further possible error handling in the target application **Cherry-pick to `5.4.x`** * * Remove unused imports * * Fix `PseudoTransactionalMessageSourceTests` for `LogAccessor` from `NullChannel` --- .../integration/channel/NullChannel.java | 21 ++++++++++--------- ...PseudoTransactionalMessageSourceTests.java | 13 +++++++----- src/reference/asciidoc/channel.adoc | 2 ++ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/channel/NullChannel.java b/spring-integration-core/src/main/java/org/springframework/integration/channel/NullChannel.java index b8594187b2..849474d3ec 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/channel/NullChannel.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/channel/NullChannel.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -18,13 +18,12 @@ package org.springframework.integration.channel; import java.util.concurrent.TimeUnit; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.springframework.beans.factory.BeanNameAware; +import org.springframework.core.log.LogAccessor; import org.springframework.integration.IntegrationPattern; import org.springframework.integration.IntegrationPatternType; import org.springframework.integration.support.management.IntegrationManagedResource; @@ -43,6 +42,8 @@ import org.springframework.messaging.PollableChannel; * Unless the payload of a sent message is a {@link Publisher} implementation, in * which case the {@link Publisher#subscribe(Subscriber)} is called to initiate * the reactive stream, although the data is discarded by this channel. + * An error thrown from a reactive stream processing (see {@link Subscriber#onError(Throwable)}) + * is logged under the {@code warn} level. * Note however that the invocations are logged at debug-level. * * @author Mark Fisher @@ -53,7 +54,7 @@ import org.springframework.messaging.PollableChannel; public class NullChannel implements PollableChannel, BeanNameAware, IntegrationManagement, IntegrationPattern { - private final Log logger = LogFactory.getLog(getClass()); + private static final LogAccessor LOG = new LogAccessor(NullChannel.class); private final ManagementOverrides managementOverrides = new ManagementOverrides(); @@ -122,8 +123,8 @@ public class NullChannel implements PollableChannel, @Override public boolean send(Message message) { - if (this.loggingEnabled && this.logger.isDebugEnabled()) { - this.logger.debug("message sent to null channel: " + message); + if (this.loggingEnabled) { + LOG.debug(() -> "message sent to null channel: " + message); } Object payload = message.getPayload(); @@ -135,12 +136,12 @@ public class NullChannel implements PollableChannel, subscription.request(Long.MAX_VALUE); } - @Override public void onNext(Object o) { + @Override public void onNext(Object value) { } - @Override public void onError(Throwable t) { - + @Override public void onError(Throwable ex) { + LOG.warn(ex, "An error happened in a reactive stream processing"); } @Override public void onComplete() { @@ -173,7 +174,7 @@ public class NullChannel implements PollableChannel, @Override public Message receive() { if (this.loggingEnabled) { - this.logger.debug("receive called on null channel"); + LOG.debug("receive called on null channel"); } incrementReceiveCounter(); return null; diff --git a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/PseudoTransactionalMessageSourceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/PseudoTransactionalMessageSourceTests.java index 3d9e0e0636..4ad2f6005b 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/endpoint/PseudoTransactionalMessageSourceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/endpoint/PseudoTransactionalMessageSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -33,6 +33,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.log.LogAccessor; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.channel.NullChannel; import org.springframework.integration.channel.QueueChannel; @@ -97,14 +98,16 @@ public class PseudoTransactionalMessageSourceTests { MessageChannel afterCommitChannel = new NullChannel(); syncProcessor.setAfterCommitChannel(afterCommitChannel); - Log logger = TestUtils.getPropertyValue(afterCommitChannel, "logger", Log.class); + LogAccessor logAccessor = TestUtils.getPropertyValue(afterCommitChannel, "LOG", LogAccessor.class); + + Log logger = logAccessor.getLog(); logger = Mockito.spy(logger); Mockito.when(logger.isDebugEnabled()).thenReturn(true); - DirectFieldAccessor dfa = new DirectFieldAccessor(afterCommitChannel); - dfa.setPropertyValue("logger", logger); + DirectFieldAccessor dfa = new DirectFieldAccessor(logAccessor); + dfa.setPropertyValue("log", logger); TransactionSynchronizationManager.initSynchronization(); TransactionSynchronizationManager.setActualTransactionActive(true); @@ -115,7 +118,7 @@ public class PseudoTransactionalMessageSourceTests { assertThat(beforeCommitMessage).isNotNull(); assertThat(beforeCommitMessage.getPayload()).isEqualTo("qox"); - Mockito.verify(logger).debug(Mockito.anyString()); + Mockito.verify(logger).debug(Mockito.any(CharSequence.class)); TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_COMMITTED); TransactionSynchronizationManager.clearSynchronization(); diff --git a/src/reference/asciidoc/channel.adoc b/src/reference/asciidoc/channel.adoc index bf87dfa0b9..33ad4118e1 100644 --- a/src/reference/asciidoc/channel.adoc +++ b/src/reference/asciidoc/channel.adoc @@ -1145,6 +1145,8 @@ For example, you can use this technique to configure a test case to verify messa Two special channels are defined within the application context by default: `errorChannel` and `nullChannel`. The 'nullChannel' (an instance of `NullChannel`) acts like `/dev/null`, logging any message sent to it at the `DEBUG` level and returning immediately. The special treatment is applied for an `org.reactivestreams.Publisher` payload of a sent message: it is subscribed to in this channel immediately, to initiate reactive stream processing, although the data is discarded. +An error thrown from a reactive stream processing (see `Subscriber.onError(Throwable)`) is logged under the warn level for possible investigation. +If there is need to do anything with such an error, the `<<./handler-advice.adoc#reactive-advice,ReactiveRequestHandlerAdvice>>` with a `Mono.doOnError()` customization can be applied to the message handler producing `Mono` reply into this `nullChannel`. Any time you face channel resolution errors for a reply that you do not care about, you can set the affected component's `output-channel` attribute to 'nullChannel' (the name, 'nullChannel', is reserved within the application context). The 'errorChannel' is used internally for sending error messages and may be overridden with a custom configuration.