Mule: Log request and response messages of synchronous call

Suppose you have a SOAP webservice running and you want to log all requests and responses to the webservice. How can you establish this using Mule ESB?


I have seen this question a lot on the mule mailinglist, and so far I have only seen answers that partially describe the solution, but I have never seen a complete example. So, this will be the topic of my first technical blog.


A typical way to do this in Mule is with the wire-tap-router. This inbound router allows you to route certain events to a different endpoint as well as to the component (see figure below).



So, what's the problem, you might think. Well, the router only taps incoming events, so this will not work for the response messages of a synchronous call. The solution is to transform the synchronous call to an asynchronous one. This can be accomplished by the async-reply router.



<model name="LogExample">
<service name="WebServiceBridge">
<inbound>
<inbound-endpoint address="${inbound.uri}" synchronous="true" />
</inbound>
<outbound>
<pass-through-router>
<vm:outbound-endpoint path="request-channel"
transformer-refs="ObjectToString SetRequestMessageProperty"/>
</pass-through-router>
</outbound>
<async-reply>
<vm:inbound-endpoint path="back-channel" />
<single-async-reply-router />
</async-reply>
</service>


As you can see, the inbound endpoint is synchronous, while the outbound endpoint isn't. The service further defines a single-async-reply-router, which means that the first message that is received is returned to the inbound-endpoint. The outbound router routes the events to the request-channel. Don't mind the transformers that are defined yet, those are explained later.


The next service that is defined is the request flow:



<service name="RequestFlow">
<inbound>
<vm:inbound-endpoint path="request-channel"/>
<wire-tap-router>
<vm:outbound-endpoint path="log-channel"/>
</wire-tap-router>
</inbound>
<outbound>
<chaining-router>
<outbound-endpoint name="webservice" address="${outbound.uri}"
synchronous="true"/>
<vm:outbound-endpoint path="response-channel"
transformer-refs="ObjectToString SetResponseMessageProperty"/>
</chaining-router>
</outbound>
</service>


A wire-tap-router is configured on the inbound-endpoint. All events are passed to the log-channel. A chaining-router is used to first send the request to the actual webservice and then send the response from the webservice to the response-channel.


The next service is the response flow:



<service name="ResponseFlow">
<inbound>
<vm:inbound-endpoint path="response-channel" />
<wire-tap-router>
<vm:outbound-endpoint path="log-channel"/>
</wire-tap-router>
</inbound>
<outbound>
<pass-through-router>
<vm:outbound-endpoint path="back-channel"/>
</pass-through-router>
</outbound>
</service>


As you can see, the event is again send to the log-channel through the wire-tap-router as well as to the back-channel (and thus to the client).


The last service we have to define is the log flow:



<service name="LogFlow">
<inbound>
<vm:inbound-endpoint path="log-channel"/>
<custom-inbound-router
class="nl.endpoint.mule.router.RequestResponseAggregator"/>
</inbound>
<component>
<spring-object bean="LogComponent" />
</component>
</service>
</model>


An aggregator is used to collect both the request and the response and hand them as a set to the LogComponent. This is only necessary if you want to handle the request and response as one message (you can also choose to log them separately). So, how can the aggregator make a distinction between the request and the response message? This is done by the transformers SetRequestMessageProperty and SetResponseMessageProperty. Both set a property to the MuleMessage indicating whether the message contains the request event or the response event. The former is shown below:



<message-properties-transformer
name="SetRequestMessageProperty">
<add-message-properties>
<spring:entry key="MessageType" value="Request"/>
</add-message-properties>
</message-properties-transformer>


The SetResponseMessageProperty is configured the same way.


Now the aggregator is pretty straight forward and listed below. The RequestResponseAggregator extends AbstractEventAggregator. Therefore the method getCorrelatorCallback has to be implemented. This method returns a new EventCorrelatorCallback. In the method aggregateEvents, the property 'MessageType' is checked.



public class RequestResponseAggregator extends AbstractEventAggregator {
@Override
protected EventCorrelatorCallback getCorrelatorCallback() {
return new EventCorrelatorCallback() {
@SuppressWarnings("unchecked")
public MuleMessage aggregateEvents(EventGroup events)
throws RoutingException {
Iterator<MuleEvent> iterator = events.iterator();
while(iterator.hasNext()) {
MuleEvent event = iterator.next();
MuleMessage message = event.getMessage();
// Check message type
if ("Request".equals(message.getProperty("MessageType"))) {
// This is the request
} else {
// This is the response
}
}

return new DefaultMuleMessage(...);
}

public EventGroup createEventGroup(MuleEvent event, Object correlationId) {
return new EventGroup(correlationId, 2);
}

public boolean shouldAggregateEvents(EventGroup events) {
return events.expectedSize() == events.size();
}
};
}
}

Hope this clarifies how to deal with logging of request and response messages in synchronous flows.


My next blog will be about another common question in the Mule mailing list: Exception Handling.



|

  • Digg
  • Del.icio.us
  • StumbleUpon
  • Reddit
  • Twitter
  • RSS

8 Response to "Mule: Log request and response messages of synchronous call"

  1. Unknown says:

    Hi Mario,

    Nice post. You can simplify things by just using a standard config element: <message-properties-transformer/> and follow the schema from there.

    Also, correlating request and response is not that trivial, one would probably need to match correlationIds too to account for multithreaded operation.

    HTH,
    Andrew

  2. Mario Klaver says:

    Hi Andrew,

    Thanks for your comments.

    You are right concerning your first comment. I altered the configuration and made it less verbose.

    Concerning your second comment, does this apply to the case when you choose not to use an aggregator? Because if you use an aggregator that extends from AbstractEventAggregator then the correlation is done for you.

    Regards,
    Mario

  3. Mario Klaver says:

    Hi Andrew,

    To clarify the above, I also added the RequestResponseAggregator in the blog. Thanks again for your comment.

    Mario

  4. Unknown says:

    Hi Mario,

    One little warning. When you use this config in Mule 2.2.1 the encoding of the response somehow gets lost. I had to set the Content-Type manually to text/xml;charset=UTF-8 by adding a transformer to the async-reply.

    Regards,

    Richard

    btw. Funny concept of holidays you have.

  5. jwang415 says:

    I'm following your example but I'm getting a java io error trying to read a closed stream on the response. It either writes the response to file or sends it back to the user. Any ideas? (I didn't include any of the aggregation content as I don't need to keep track of which request goes with which response)

  6. Mario Klaver says:

    Hi jwang,

    Did you use the ObjectToString transformer on the outbound-endpoint's as stated in the example? (don't forget to define the transformer at the top of your config file.

     object-to-string-transformer name="ObjectToString"/>

    This way the stream is converted to a string and can be read twice without any problems.

    If this doesn't work, can you post your mule-config.xml file?

  7. jwang415 says:

    Thanks, That worked great!!!

  8. Unknown says:

    Thanks for the code! I'd like to try this later!

    infomercial producer

Post a Comment