0

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.

al.truisme
  • 450
  • 2
  • 11
  • After some checking and head-scratching, it seems that ```handleRequestMessage()``` cannot be proxied because it is protected. Will need to find another method to pointcut. – al.truisme Sep 06 '22 at 15:55

2 Answers2

1

The handleRequestMessage() is essentially an input message to this gateway and output. So, if you don't like implementing an AbstractRequestHandlerAdvice and adding it into each your gateway via their <request-handler-advice-chain>, then consider to use a <wire-tap> for input and output channels of those gateway.

You may implement, though, a BeanPostProcessor.postProcessBeforeInitialization() to add your custom AbstractRequestHandlerAdvice into those HTTP gateways you are interested in.

My point is that <aop:aspect> you are presenting us really might lead to some unexpected behavior, like that final method concern you have edit out from your question...

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • I removed the part about the DEBUG messages because I was afraid that it was a distraction. I'm going to look into the PostProcessor. (I had been thinking about that prior and you've confirmed that.). I've looked at WireTap but it's not called for the gateways themselves hence would require some specific convention for the channel names before/after the gateway calls. – al.truisme Sep 06 '22 at 16:24
  • The ```AbstractRequestHandlerAdvice``` added to the gateways looks like it will work for introspecting the input message and the result of the HTTP calls. I was also hoping to log some information about the HTTP, including the URL and the METHOD. I see that ```AbstractRequestHandlerAdvice``` receives the information about the gateway component via the ```target``` argument, but that this argument type is a private type ```AdvisedRequestHandler```, which makes the introspection a bit harder. Is there a recommended way of retrieving this information for logging? – al.truisme Sep 07 '22 at 13:27
  • If you want exactly only an HTTP level, then you need to look into instrumenting a `RestTemplate` you can can inject into those HTTP Outbound Gateways. See its `ClientHttpRequestInterceptor` feature. – Artem Bilan Sep 07 '22 at 14:25
  • Wanting to log the message, response and the http call particulars. I see that the advice gives me what I need for the message and the response, so that part is solved. I can also retrieve the ```HttpRequestExecutingMessageHandler``` via ```RequestHandler.getAdvisedHandler()``` but now seeing that the ```HttpRequestExecutingMessageHandler``` has only private members so can't retrieve via that class. I will look at ```ClientHttpRequestInterceptor``` as you indicate. Thanks very much. – al.truisme Sep 07 '22 at 14:51
0

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;
    }

}
al.truisme
  • 450
  • 2
  • 11