Search code examples
springloggingspring-integrationaopaspectj

Spring Integration AOP for Logging outbound Http requests


I was looking at a post from 2014 about using Spring AOP for logging HTTP requests/replies:

Spring integration + logging response time for http adapters(or any endpoint)

To this end, I tried this AOP configuration:

<aop:config >
    <aop:aspect id="myAspect" ref="inboundOutboundHttpLogging">
        <aop:pointcut id="handleRequestMessageMethod"
                      expression="execution(* org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleRequestMessage(*))
                                  and
                                  args(message))" />
        <aop:before method="requestMessageSent" pointcut-ref="handleRequestMessageMethod" arg-names="message"/>
    </aop:aspect>
</aop:config>

Is there perhaps a newer way of using AOP for logging HTTP requests? I want to avoid having to put per-request logging (i.e. outbound-gateway advice on each gateway).

Thanks for any pointers.


Solution

  • Based upon the suggestions made by @artem-bilan, I was able to find a solution similar to AOP for injecting logging AbstractRequestHandlerAdvice into HTTP outbound request processing. I'm contributing this as a way of showing a possible solution for anyone else who comes across this question.

    As @artem-bilan mentions, there is a mechanism for injecting AbstractRequestHandlerAdvice into a AbstractReplyProducingMessageHandler such as an HttpRequestExecutingMessageHandler. In my case, I'm wanting to log the message contents (header and payload) prior to the HTTP call and also log the return message (header and payload). This works nicely.

    @artem-bilan suggests that the BeanPostProcessor mechanism can allow to inject the advice without having to add that declaration to each http outbound bean. The BeanPostProcessor looks like this:

    public class AddHttpOutboundAdvicePostProcessor implements BeanPostProcessor {
        final List<Advice> adviceList;
    
        final AddHttpOutboundAdvicePostProcessor(List<Advice> adviceList) {
            this.adviceList = adviceList;
        }
    
        @Override
        public Object postProcessAfterInitialization(@NonNull Object bean, 
                                                     @NonNull String beanName) 
                                                                 throws BeansException {
            if (bean instanceof AbstractHttpRequestExecutingMessageHandler) {
                ((AbstractHttpRequestExecutingMessageHandler) bean).setAdviceChain(adviceList);
            }
            return bean;
        }
    }
    

    We need to set up this bean into our context. (I'm a die-hard declarative fan hence this is in XML.)

    <bean id    = "addHttpLoggingPostProcessor"
          class = "com.my.package.AddHttpOutboundAdvicePostProcessor" >
        <constructor-arg name="adviceList>
            <util:list>
                <ref bean="outboundLogger" />
            </util:list>
        </constructor-arg>
    </bean>
    

    Here, the outboundLogger is a bean that managers the request-handler-advice. In my choice of implementation, I'm sending a copy of the outbound message to a channel for logging beforehand, and a copy of the response message down another channel for logging the response. The XML declaration of the bean takes the two channel names as constructors:

    <bean id="outboundLogger" class="com.my.package.HttpRequestProcessorLogger" >
        <constructor-arg name="requestLoggingChannelName"  value="XXX" />
        <constructor-arg name="responseLoggingChannelName" value="YYY" />
    </bean>
    

    where XXX and YYY are the names of channels to the components that perform the logging. I've set these channels to be ExecutorChannels so that the logging is performed asynchronously.

    The HttpRequestProcessorLogger bean manages the call to handleRequestMessage():

    public class HttpRequestProcessorLogger extends AbstractRequestHandlerAdvice {
        private MessageChannel requestLoggingChannel;
        private MessageChannel responseLoggingChannel;
    
        private String requestLoggingChannelName;
        private String responseLoggingChannelName;
    
        private BeanFactory beanFactory;
    
        public HttpRequestProcessorLogger(String requestLoggingChannelName, String responseLoggingChannelName) {
            this.requestLoggingChannelName  = requestLoggingChannelName;
            this.responseLoggingChannelName = responseLoggingChannelName;
        }
    
        @Override
        protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
            getChannels();
            requestLoggingChannel.send(message);
            final Object result = callback.execute();
            final message<?> outputMessage = 
                (MessageBuilder.class.isInstance(result) ? ((MessageBuilder<?>) result).build()
                                                         : (Message<?>) result;
            responseLoggingChannel.send(outputMessage);
            return outputMessage;
        }
    
        private synchronized void getChannels() {
            if (requestLoggingChannelName != null) {
                final DestinationResolver<MessageChannel> 
                    channelResolver = ChannelResolverUtils.getChannelResolver(this.beanFactory);
                requestLoggingChannel  = channelResolver.resolverDestination(requestLoggingChannelName);
                responseLoggingChannel = channelResolver.resolverDestination(responseLoggingChannelName);
                requestLoggingChannelName  = null;
                responseLoggingChannelName = null;
            } 
        }
    
        @Override
        public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeanException {
            this.beanFactory = beanFactory;
        }
    
    }