I recently worked with a client on a Mule ESB project that had a requirement to catch unexpected errors and send a formatted email with the error details. This requirement seemed simple enough, so we created a global exception strategy and referred to it from all of our flows.
The error handler looked something like this:
<catch-exception-strategy name="catchUnexpectedError">
<parse-template location="templates/error-notification-email.txt"/>
<smtp:outbound-endpoint name="unexpectedExceptionEmail"
host="${email.host}"
port="${email.port}"
to="${error.to}"
from="${error.from}"
subject="${generic.error.subject}"/>
</catch-exception-strategy>
What we quickly realized when we started testing the application in a test environment was that in certain "failure" situations, there were several errors that were generated. The result of this was that several error emails were being sent. This was not the desired behavior, the desired behavior was to only send one email with all of the error messages in it. So what did we do? We employed the use of a collection aggregator to collect all of the exceptions to create one single email. Here are the steps we took to accomplish this:
1. We set the MULE_CORRELATION_ID property in the initial flow that was being triggered.
<set-property propertyName="MULE_CORRELATION_ID" value="#[java.util.UUID.randomUUID().toString()]"/>
2. We set the MULE_CORRELATION_GROUP_SIZE property right after that.
<set-property propertyName="MULE_CORRELATION_GROUP_SIZE" value="#[java.lang.Integer.MAX_VALUE]"/>
3. Changed the global exception strategy to look like this:
<catch-exception-strategy name="catchUnexpectedError">
<processor-chain>
<collection-aggregator failOnTimeout="false" timeout="${error.aggregator.timeout}"/>
<transformer ref="unexpectedErrorTransformer"/>
<parse-template location="templates/error-notification-email.txt"/>
<smtp:outbound-endpoint name="unexpectedExceptionEmail"
host="${email.host}"
port="${email.port}"
to="${error.to}"
from="${error.from}"
subject="${generic.error.subject}"/>
</processor-chain>
</catch-exception-strategy>
The next time we tested the application in a scenario where several errors were generated, only one email was sent with all of the error messages in it. So, how did this work?
Setting the MULE_CORRELATION_ID property attaches to all messages that spawn from the flow where it is set an id that "links" the messages together. Setting the MULE_CORRELATION_GROUP_SIZE property tells the collection aggregator how large the collection sent to it could be. In this case, we had no clue how many possible errors could be sent, so we set this to a size that would likely never be exceeded. How would this ever work then? We set a timeout on the collection aggregator that would continue with the processing of the exception strategy even when the collection size didn't meet the size of the MULE_CORRELATION_GROUP_SIZE property (which was the case in this example). Also to note, we used a custom transformer to process the message collection to set the payload for the template to be emailed.
This transformer is written in Groovy and looks like this:
import groovy.util.logging.Slf4j
import org.mule.api.MuleMessage
import org.mule.api.transformer.TransformerException
import org.mule.transformer.AbstractMessageTransformer
import org.springframework.stereotype.Component
@Slf4j
@Component("unexpectedErrorTransformer")
class UnexpectedErrorTransformer extends AbstractMessageTransformer {
public static final String LINE_BREAK = System.getProperty('line.separator')
@Override
Object transformMessage(MuleMessage muleMessage, String s) throws TransformerException {
org.mule.DefaultMessageCollection messageCollection = muleMessage
List messages = messageCollection.messageList
StringBuilder builder = new StringBuilder()
int errorCount = 1
messages.collect({
builder.append(buildErrorMessage(errorCount++,it))
})
muleMessage.setPayload(builder.toString())
return muleMessage
}
String buildErrorMessage(int errorCount,MuleMessage aMessage){
StringBuilder errorMessage = new StringBuilder()
errorMessage.append(errorCount + ". The following unexpected error occurred:")
errorMessage.append(LINE_BREAK)
errorMessage.append(aMessage.exceptionPayload?.exception?.summaryMessage)
errorMessage.append(LINE_BREAK)
return errorMessage.toString()
}
}
Also, to give you a little more context, this is what the email template looked like:
The following unexpected error(s) have occurred:
#[payload]
I hope that this helps you out if you ever run into a similar situation when dealing with errors in a Mule ESB project.