= Message History

Camel supports the
https://www.enterpriseintegrationpatterns.com/patterns/messaging/MessageHistory.html[Message History]
from the xref:enterprise-integration-patterns.adoc[EIP patterns] book.

The Message History from the EIP patterns allows for analyzing and debugging the flow of messages in a loosely coupled system.

image::eip/MessageHistory.gif[image]

Attaching a Message History to the message will provide a list of all applications that the message passed through since its origination.

== Enabling Message History

The message history is disabled by default (to optimize for lower footprint out of the box). You should only enable message history
if needed, such as during development, where Camel can report route stack-traces when a message failed with an exception.
But for production usage, then message history should only be enabled if you have monitoring systems that rely on gathering these
fine-grained details. When message history is enabled then there is a slight performance overhead as the history data is stored
in a `java.util.concurrent.CopyOnWriteArrayList` due to the need of being thread safe.

The Message History can be enabled or disabled per CamelContext or per route (disabled by default).
For example, you can turn it on with:

[source,java]
----
camelContext.setMessageHistory(true);
----

Or in XML

[source,xml]
----
<camelContext messageHistory="true">

</camelContext>
----

Or when using Spring Boot or Quarkus, you can enable this in the configuration file:

[source,properties]
----
camel.springboot.message-history = true
----

And in Quarkus:

[source,properties]
----
camel.quarkus.message-history = true
----

=== Route level Message History

You can also enable or disable message history per route.
When doing this then Camel can only gather message history in the routes where this is enabled,
which means you may not have full coverage. You may still want to do this, for example to capture
the history in a critical route to help pin-point where the route is slow.

A route level configuration overrides the global configuration.

To enable in Java:

[source,java]
----
from("jms:cheese")
  .messageHistory()
  .to("bean:validate")
  .to("bean:transform")
  .to("jms:wine");
----

You can also turn off message history per route:

[source,java]
----
from("jms:cheese")
  .messageHistory(false)
  .to("bean:validate")
  .to("bean:transform")
  .to("jms:wine");
----

And in XML:

[source,xml]
----
<route messageHistory="true">
  <from uri="jms:cheese"/>
  <to uri="bean:validate"/>
  <to uri="bean:transform"/>
  <to uri="jms:wine"/>
</route>
----

== Route stack-trace in exceptions logged by error handler

If Message History is enabled, then Camel will include this information,
when the xref:latest@manual:ROOT:error-handler.adoc[Error Handler] logs exhausted exceptions,
where you can see the message history; you may think this as a "route stacktrace".

And example is provided below:

[source,text]
----
2020-01-15 11:58:34,834 [read #3 - Delay] ERROR DefaultErrorHandler            - Failed delivery for (MessageId: ID-davsclaus-pro-local-1579085914402-0-2 on ExchangeId: ID-davsclaus-pro-local-1579085914402-0-3). Exhausted after delivery attempt: 1 caught: java.lang.IllegalArgumentException: Forced to dump message history

Message History
---------------------------------------------------------------------------------------------------------------------------------------
RouteId              ProcessorId          Processor                                                                        Elapsed (ms)
[route1            ] [route1            ] [from[seda://start]                                                            ] [       432]
[route1            ] [to1               ] [log:foo                                                                       ] [         5]
[route1            ] [to2               ] [direct:bar                                                                    ] [       111]
[route2            ] [to5               ] [log:bar                                                                       ] [         0]
[route2            ] [delay2            ] [delay[100]                                                                    ] [       110]
[route2            ] [to6               ] [mock:bar                                                                      ] [         0]
[route1            ] [delay1            ] [delay[300]                                                                    ] [       305]
[route1            ] [to3               ] [log:baz                                                                       ] [         2]
[route1            ] [process1          ] [Processor@0x5e600dd5                                                          ] [         0]

Stacktrace
---------------------------------------------------------------------------------------------------------------------------------------
java.lang.IllegalArgumentException: Forced to dump message history
	at org.apache.camel.processor.MessageHistoryDumpRoutingTest$1$1.process(MessageHistoryDumpRoutingTest.java:52) ~[test-classes/:?]
	at org.apache.camel.support.processor.DelegateSyncProcessor.process(DelegateSyncProcessor.java:64) ~[classes/:?]
	at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryState.run(RedeliveryErrorHandler.java:477) ~[classes/:?]
	at org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.schedule(DefaultReactiveExecutor.java:186) ~[classes/:?]
	at org.apache.camel.impl.engine.DefaultReactiveExecutor.schedule(DefaultReactiveExecutor.java:56) ~[classes/:?]
	at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryState.lambda$run$1(RedeliveryErrorHandler.java:481) ~[classes/:?]
	at org.apache.camel.processor.DelayProcessorSupport$ProcessCall$1.done(DelayProcessorSupport.java:77) [classes/:?]
	at org.apache.camel.support.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:63) [classes/:?]
	at org.apache.camel.processor.DelayProcessorSupport$ProcessCall.run(DelayProcessorSupport.java:70) [classes/:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_201]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_201]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [?:1.8.0_201]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [?:1.8.0_201]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_201]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_201]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_201]
----

When Message History is enabled then the full history is logged as shown above. Here we can see the full path
the message has been routed, where it started from route1, called route2 and returned to route1 again, and so on.

When Message History is disabled (by default) then the error handler logs a brief history with the last node
where the exception occurred as shown below:

[source,text]
----
2020-01-15 11:59:33,238 [read #3 - Delay] ERROR DefaultErrorHandler            - Failed delivery for (MessageId: ID-davsclaus-pro-local-1579085972821-0-2 on ExchangeId: ID-davsclaus-pro-local-1579085972821-0-3). Exhausted after delivery attempt: 1 caught: java.lang.IllegalArgumentException: Forced to dump message history

Message History (complete message history is disabled)
---------------------------------------------------------------------------------------------------------------------------------------
RouteId              ProcessorId          Processor                                                                        Elapsed (ms)
[route1            ] [route1            ] [from[seda://start]                                                            ] [       419]
	...
[route1            ] [process1          ] [Processor@0x229c6181                                                          ] [         0]

Stacktrace
---------------------------------------------------------------------------------------------------------------------------------------
java.lang.IllegalArgumentException: Forced to dump message history
	at org.apache.camel.processor.MessageHistoryDumpRoutingTest$1$1.process(MessageHistoryDumpRoutingTest.java:52) ~[test-classes/:?]
	at org.apache.camel.support.processor.DelegateSyncProcessor.process(DelegateSyncProcessor.java:64) ~[classes/:?]
	at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryState.run(RedeliveryErrorHandler.java:477) ~[classes/:?]
	at org.apache.camel.impl.engine.DefaultReactiveExecutor$Worker.schedule(DefaultReactiveExecutor.java:186) ~[classes/:?]
	at org.apache.camel.impl.engine.DefaultReactiveExecutor.schedule(DefaultReactiveExecutor.java:56) ~[classes/:?]
	at org.apache.camel.processor.errorhandler.RedeliveryErrorHandler$RedeliveryState.lambda$run$1(RedeliveryErrorHandler.java:481) ~[classes/:?]
	at org.apache.camel.processor.DelayProcessorSupport$ProcessCall$1.done(DelayProcessorSupport.java:77) [classes/:?]
	at org.apache.camel.support.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:63) [classes/:?]
	at org.apache.camel.processor.DelayProcessorSupport$ProcessCall.run(DelayProcessorSupport.java:70) [classes/:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_201]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_201]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [?:1.8.0_201]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [?:1.8.0_201]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_201]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_201]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_201]
----

Here you can see the Message History only outputs the input (route1) and the last step
where the exception occurred (process1).

=== Configuring route stack-trace from error handler

You can turn off logging Message History with `logExhaustedMessageHistory`
from the xref:latest@manual:ROOT:error-handler.adoc[Error Handler] using:

[source,java]
----
errorHandler(defaultErrorHandler().logExhaustedMessageHistory(false));
----

The xref:latest@manual:ROOT:error-handler.adoc[Error Handler] does not log the
message body/header details (to avoid logging sensitive message body details).
You can enable this with `logExhaustedMessageBody` on the error handler as shown:

[source,java]
----
errorHandler(defaultErrorHandler().logExhaustedMessageBody(true));
----

In XML configuring this is a bit different, as you configure this in the `redeliveryPolicy`
of the `<errorHandler>` as shown:

[source,xml]
----
<camelContext messageHistory="true" errorHandlerRef="myErrorHandler" xmlns="http://camel.apache.org/schema/spring">

    <errorHandler id="myErrorHandler">
      <redeliveryPolicy logExhaustedMessageHistory="false" logExhaustedMessageBody="true"/>
    </errorHandler>

    <route>
      <from uri="jms:cheese"/>
      <to uri="bean:validate"/>
      <to uri="bean:transform"/>
      <to uri="jms:wine"/>
    </route>
</camelContext>
----

== MessageHistory API

When message history is enabled during routing Camel captures how the `Exchange` is routed,
as an `org.apache.camel.MessageHistory` entity that is stored on the `Exchange`.

On the `org.apache.camel.MessageHistory` there is information about the
route id, processor id, timestamp, and elapsed time it took to process the `Exchange` by the processor.

You can access the message history from Java code:

[source,java]
----
List<MessageHistory> list = exchange.getProperty(Exchange.MESSAGE_HISTORY, List.class);
for (MessageHistory history : list) {
    System.out.println("Routed at id: " + history.getNode().getId());
}
----
