Correlating logs from redelivered messages made easier in Camel

Apache Camel handles redelivery of messages based on two principles
- internally
- externally

The internal redelivery is essentially the Camel error handler, that allows you to retry processing a failed message. This is all handled by Camel itself.

However the situation changes when the redelivery is commenced externally, such as from a message broker like Apache ActiveMQ. Previously from a Camel point of view, when the message broker was redelivering a message, it was regarded as a new message.

This made it a bit harder for end users of Camel to figure out this was a redelivered message, and also how to correlate logs.

This has been improved in the upcoming releases of Apache Camel and Fuse Mediation Router.

Given you have configured a Camel route to consume messages from ActiveMQ and use transactions as shown in the following snippet.

<route id="myRoute">
  <from uri="activemq:queue:foo"/>
  <process ref="myProcessor"/>
  <process ref="myOtherProcessor"/>

The route is kept simple for demonstrations purpose. However it indicate the route is transacted, which allows Camel to support externally redeliveries. 

We have configured the Camel activemq component to use JMS transacted acknowledgment (transacted=true)

<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
  <property name="connectionFactory" ref="poolConnectionFactory"/>
  <property name="transacted" value="true"/>
  <property name="transactionManager" ref="jmsTransactionManager"/>

Now suppose Camel consumes a message from the foo queue. And processing the message fails upon the first two attempts, and on the 3rd attempt the message is processed successfully. 

Camel will by default log at WARN level when a transaction is rolled back. What has been improved is the details in this log, which now includes information whether the message was externally redelivered, as well both the message id and exchange id. This allows you to correlate the logs. More about this a bit later. Notice in the 1st attempt, we have redelivered(false), and in the 2nd attempt its redelivered(true). Also notice that the MessageID is the same, where as ExchangeID is unique.

1st attempt
Transaction rollback (0x3e4ac866) redelivered(false) for (MessageId: ID:davsclaus.lan-55814-1328257420734-2:2:1:1:1 on ExchangeId: ID-davsclaus-lan-55809-1328257419533-0-3) caught: java.lang.IllegalArgumentException: Forced exception

2nd attempt
Transaction rollback (0x3e4ac866) redelivered(true) for (MessageId: ID:davsclaus.lan-55814-1328257420734-2:2:1:1:1 on ExchangeId: ID-davsclaus-lan-55809-1328257419533-0-4) caught: java.lang.IllegalArgumentException: Forced exception

3rd attempt
Success, no WARN logging.

Configuring logging details
When a transaction is rolled back, Camel will not log the stracktrace, so its less noisy. But you can configure the logging details from the Camel transaction error handler as shown below. If you want to see the failed exception and stack trace, then you can set the logStackTrace and logExhausted options to true.

<errorHandler id="txEH" type="TransactionErrorHandler" rollbackLoggingLevel="WARN">
  <redeliveryPolicy logStackTrace="false" logExhausted="false"/>

That is not all, the activemq component can also configure logging details. The activemq component will be default log any uncaught exceptions at WARN level including stack traces. So out of the box you may see the exception being logged twice. One time by the Camel transaction error handler, and a 2nd time by the activemq component. You can customize the logging level on the activemq, so for example to suppress logging, you can disable logging using the two errorHandler options as shown below:

<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
  <property name="connectionFactory" ref="poolConnectionFactory"/>
  <property name="transacted" value="true"/>
  <property name="transactionManager" ref="jmsTransactionManager"/>
  <property name="errorHandlerLoggingLevel" value="OFF"/>
  <property name="errorHandlerLogStackTrace" value="false"/>

Okay so lets take a look at how to correlate the logs.

Correlating logs
When ActiveMQ redeliveries a message, it will use the same MessageID, which allows you to correlate the logs. So in the example above. You can correlate the message by MessageID, which ends with 2:2:1:1. It can get even easier if you enable MDC logging, which means you can have a column in the log file, that contains the MessageID.

Here is a example where we use MDC logging, and have exchangeId and messageId in two columns. We have shortened the ids, as the are often unique in the last part. So we have kept the last 10 chars, configured by %-10.10X{exchangeId} - %-10.10X{messageId}. I also added a route step to log the message using <to uri="log:before"/>.

1st message
INFO  before                         - 471691-0-3 - -2:2:1:1:1 - Exchange[ExchangePattern:InOnly, BodyType:String, Body:Hello World]
WARN  TransactionErrorHandler        - 471691-0-3 - -2:2:1:1:1 - Transaction rollback (0x4ab34646) redelivered(false) for (MessageId: ID:davsclaus.lan-56013-1328258472923-2:2:1:1:1 on ExchangeId: ID-davsclaus-lan-56008-1328258471691-0-3) caught: java.lang.IllegalArgumentException: Forced exception

2nd messge
INFO  before                         - 471691-0-4 - -2:2:1:1:1 - Exchange[ExchangePattern:InOnly, BodyType:String, Body:Hello World]
WARN  TransactionErrorHandler        - 471691-0-4 - -2:2:1:1:1 - Transaction rollback (0x4ab34646) redelivered(true) for (MessageId: ID:davsclaus.lan-56013-1328258472923-2:2:1:1:1 on ExchangeId: ID-davsclaus-lan-56008-1328258471691-0-4) caught: java.lang.IllegalArgumentException: Forced exception

3rd message
INFO  before                         - 471691-0-5 - -2:2:1:1:1 - Exchange[ExchangePattern:InOnly, BodyType:String, Body:Hello World]

So what you can see is that the exchangeID is unique, as it goes 0-3, 0-4, 0-5. Where as MessageID is the same as its 2:2:1:1:1.

That is not all, I have also added a new JMX attribute, so you from JMX can see how many external redelivered messages a Camel route has processed.

JMX Statistics
So the JMX statistics for the route would be as follows for this example.

1st message
ExchangesTotal: 1
ExchangesCompleted: 0
ExchangesFailed: 1
ExternalRedeliveries: 0
Redeliveries: 0  (internal)

2nd message
ExchangesTotal: 2
ExchangesCompleted: 0
ExchangesFailed: 2
ExternalRedeliveries: 1
Redeliveries: 0  (internal)

3rd message
ExchangesTotal: 3
ExchangesCompleted: 1
ExchangesFailed: 2
ExternalRedeliveries: 2
Redeliveries: 0  (internal)

The new attribute is ExternalRedeliveries. Notice in the 3rd message, we can see that we have processed 3 messages, however 2 of them failed, and 1 was successfully. Beforehand you could not see from this information that it was in face the same message. It could just as well be 3 different messages, where it just happens only 1 could be processed successfully. Now you can know how many unique messages a route has processed by calculating: ExchangesTotal - ExternalRedeliveries. So in our example that would mean: 3 - 2 = 1.

I am considering improving this a bit more. I fell that that Camel should log at INFO level if a external redelivered message was committed. This would allow you to see this from the logs. This allows you to know that the message was eventually processed successfully. As the alternative would be that ActiveMQ would move the message to its Dead Letter Queue. And when this happens, Camel would not know, and you cannot see this from the Camel logs. Likewise I consider making adjusting the logging levels for transaction begin and commit configurable. They are currently fixed at DEBUG level.

If you have feedback upon this then feel free to comment on the blog, or reach out to the Camel community.


Christian Schneider said...

Hi Claus,

great new feature. Does this also allow to configure the maximumRedeliveries in onException so you can react on the number of external redeliveries?

While you are correct that ActiveMQ would put the message to the DLQ at some point not every jms provider supports that.

I once did this by evaluating the jms header for the number of redeliveries but it would be nice if camel would handle that.


Claus Ibsen said...

Currently you cannot. An external redelivery is like a total new message for Camel.

However I did look into to obtain the redelivery counter from the JMS broker. But that is not standard from the JMS spec.

ActiveMQ do have a redelivery counter, but you can only get it using ActiveMQ API, so eg you would need to hack in the activemq-camel component for that.

That said, we could in the future allow to let Camel know about this redelivery counter, and have its onException use that as well.

Marco Zapletal said...

Hi Claus,

this is a cool new feature. Thanks.

>That said, we could in the future
>allow to let Camel know about this
>redelivery counter, and have its
>onException use that as well.

This would be great and I suppose that it would be interesting for a lot of users. I am looking forward to it ;-)


nvp152 said...

With the latest Camel version are we still not able to leverage this external redelivery counter?