This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit af01508713924648345a4a4198debae3db9898df Author: Claus Ibsen <[email protected]> AuthorDate: Mon Feb 13 13:54:14 2023 +0100 CAMEL-19040: Backlog tracer - Capture exception and also first/last to know better what is input and output from Camel. --- .../java/org/apache/camel/spi/BacklogTracer.java | 10 +++ .../camel/spi/BacklogTracerEventMessage.java | 40 +++++++++ .../camel/impl/debugger/BacklogDebugger.java | 6 +- .../apache/camel/impl/debugger/BacklogTracer.java | 11 +++ .../debugger/DefaultBacklogTracerEventMessage.java | 94 +++++++++++++++++++++- .../camel/impl/engine/CamelInternalProcessor.java | 63 ++++++++++++--- .../camel/management/BacklogTracerFilterTest.java | 2 +- .../apache/camel/management/BacklogTracerTest.java | 42 +++++----- .../camel/management/ManagedFromRestGetTest.java | 4 +- .../management/ManagedFromRestPlaceholderTest.java | 4 +- .../org/apache/camel/support/MessageHelper.java | 82 +++++++++++++++++++ 11 files changed, 320 insertions(+), 38 deletions(-) diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java index 580955018b0..9ff19a3aa21 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracer.java @@ -109,6 +109,16 @@ public interface BacklogTracer { */ void setIncludeExchangeProperties(boolean includeExchangeProperties); + /** + * Trace messages to include exception if the message failed + */ + boolean isIncludeException(); + + /** + * Trace messages to include exception if the message failed + */ + void setIncludeException(boolean includeException); + /** * Filter for tracing by route or node id */ diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracerEventMessage.java b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracerEventMessage.java index 6b2d66e8a07..95e7da612c7 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracerEventMessage.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/BacklogTracerEventMessage.java @@ -31,6 +31,16 @@ public interface BacklogTracerEventMessage { */ long getUid(); + /** + * Whether this is a new incoming message and this is the first trace. + */ + boolean isFirst(); + + /** + * Whether this is the last trace of the message (its complete). + */ + boolean isLast(); + /** * Timestamp of the traced event */ @@ -61,6 +71,36 @@ public interface BacklogTracerEventMessage { */ String getMessageAsJSon(); + /** + * Time elapsed for processing the given node (in millis). + */ + long getElapsed(); + + /** + * Whether the message is done processing the given node + */ + boolean isDone(); + + /** + * Did the message fail during processing (i.e. was an exception thrown) + */ + boolean isFailed(); + + /** + * Was there an exception thrown during processing + */ + boolean hasException(); + + /** + * The exception as XML (exception type, message and stacktrace) + */ + String getExceptionAsXml(); + + /** + * The exception as JSon (exception type, message and stacktrace) + */ + String getExceptionAsJSon(); + /** * Dumps the event message as XML using the {@link #ROOT_TAG} as root tag. * <p/> diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogDebugger.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogDebugger.java index 18c92cf43cd..cb1e41fc048 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogDebugger.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogDebugger.java @@ -608,7 +608,7 @@ public final class BacklogDebugger extends ServiceSupport { suspendedBreakpointMessages.computeIfPresent( nodeId, (nId, message) -> new DefaultBacklogTracerEventMessage( - message.getUid(), message.getTimestamp(), message.getRouteId(), message.getToNode(), + false, false, message.getUid(), message.getTimestamp(), message.getRouteId(), message.getToNode(), message.getExchangeId(), dumpAsXml(suspendedExchange.getExchange()), dumpAsJSon(suspendedExchange.getExchange()))); @@ -670,7 +670,7 @@ public final class BacklogDebugger extends ServiceSupport { BacklogTracerEventMessage msg = new DefaultBacklogTracerEventMessage( - uid, timestamp, routeId, toNode, exchangeId, messageAsXml, messageAsJSon); + false, false, uid, timestamp, routeId, toNode, exchangeId, messageAsXml, messageAsJSon); suspendedBreakpointMessages.put(nodeId, msg); // suspend at this breakpoint @@ -738,7 +738,7 @@ public final class BacklogDebugger extends ServiceSupport { BacklogTracerEventMessage msg = new DefaultBacklogTracerEventMessage( - uid, timestamp, routeId, toNode, exchangeId, messageAsXml, messageAsJSon); + false, false, uid, timestamp, routeId, toNode, exchangeId, messageAsXml, messageAsJSon); suspendedBreakpointMessages.put(toNode, msg); // suspend at this breakpoint diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogTracer.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogTracer.java index a89131c84c9..f03e661c748 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogTracer.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/BacklogTracer.java @@ -61,6 +61,7 @@ public final class BacklogTracer extends ServiceSupport implements org.apache.ca private boolean bodyIncludeStreams; private boolean bodyIncludeFiles = true; private boolean includeExchangeProperties = true; + private boolean includeException = true; // a pattern to filter tracing nodes private String tracePattern; private String[] patterns; @@ -233,6 +234,16 @@ public final class BacklogTracer extends ServiceSupport implements org.apache.ca this.includeExchangeProperties = includeExchangeProperties; } + @Override + public boolean isIncludeException() { + return includeException; + } + + @Override + public void setIncludeException(boolean includeException) { + this.includeException = includeException; + } + @Override public String getTracePattern() { return tracePattern; diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java index 2b1a7009eb7..7f1bff48b3d 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/debugger/DefaultBacklogTracerEventMessage.java @@ -20,6 +20,7 @@ import java.text.SimpleDateFormat; import java.util.Map; import org.apache.camel.spi.BacklogTracerEventMessage; +import org.apache.camel.util.StopWatch; import org.apache.camel.util.json.JsonObject; import org.apache.camel.util.json.Jsonable; import org.apache.camel.util.json.Jsoner; @@ -29,6 +30,9 @@ import org.apache.camel.util.json.Jsoner; */ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEventMessage { + private final StopWatch watch; + private final boolean first; + private final boolean last; private final long uid; private final long timestamp; private final String routeId; @@ -36,9 +40,18 @@ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEven private final String exchangeId; private final String messageAsXml; private final String messageAsJSon; - - public DefaultBacklogTracerEventMessage(long uid, long timestamp, String routeId, String toNode, String exchangeId, + private String exceptionAsXml; + private String exceptionAsJSon; + private long duration; + private boolean done; + private boolean failed; + + public DefaultBacklogTracerEventMessage(boolean first, boolean last, long uid, long timestamp, + String routeId, String toNode, String exchangeId, String messageAsXml, String messageAsJSon) { + this.watch = new StopWatch(); + this.first = first; + this.last = last; this.uid = uid; this.timestamp = timestamp; this.routeId = routeId; @@ -48,11 +61,29 @@ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEven this.messageAsJSon = messageAsJSon; } + /** + * Callback when the message has been processed at the given node + */ + public void doneProcessing() { + done = true; + duration = watch.taken(); + } + @Override public long getUid() { return uid; } + @Override + public boolean isFirst() { + return first; + } + + @Override + public boolean isLast() { + return last; + } + @Override public long getTimestamp() { return timestamp; @@ -83,6 +114,43 @@ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEven return messageAsJSon; } + @Override + public long getElapsed() { + return done ? duration : watch.taken(); + } + + @Override + public boolean isDone() { + return done; + } + + public boolean isFailed() { + return hasException(); + } + + @Override + public boolean hasException() { + return exceptionAsXml != null || exceptionAsJSon != null; + } + + @Override + public String getExceptionAsXml() { + return exceptionAsXml; + } + + public void setExceptionAsXml(String exceptionAsXml) { + this.exceptionAsXml = exceptionAsXml; + } + + @Override + public String getExceptionAsJSon() { + return exceptionAsJSon; + } + + public void setExceptionAsJSon(String exceptionAsJSon) { + this.exceptionAsJSon = exceptionAsJSon; + } + @Override public String toString() { return "DefaultBacklogTracerEventMessage[" + exchangeId + " at " + toNode + "]"; @@ -105,8 +173,13 @@ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEven StringBuilder sb = new StringBuilder(); sb.append(prefix).append("<").append(ROOT_TAG).append(">\n"); sb.append(prefix).append(" <uid>").append(uid).append("</uid>\n"); + sb.append(prefix).append(" <first>").append(first).append("</first>\n"); + sb.append(prefix).append(" <last>").append(last).append("</last>\n"); String ts = new SimpleDateFormat(TIMESTAMP_FORMAT).format(timestamp); sb.append(prefix).append(" <timestamp>").append(ts).append("</timestamp>\n"); + sb.append(prefix).append(" <elapsed>").append(getElapsed()).append("</elapsed>\n"); + sb.append(prefix).append(" <done>").append(isDone()).append("</done>\n"); + sb.append(prefix).append(" <failed>").append(isFailed()).append("</failed>\n"); // route id is optional and we then use an empty value for no route id sb.append(prefix).append(" <routeId>").append(routeId != null ? routeId : "").append("</routeId>\n"); if (toNode != null) { @@ -117,6 +190,9 @@ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEven } sb.append(prefix).append(" <exchangeId>").append(exchangeId).append("</exchangeId>\n"); sb.append(prefix).append(messageAsXml).append("\n"); + if (exceptionAsXml != null) { + sb.append(prefix).append(exceptionAsXml).append("\n"); + } sb.append(prefix).append("</").append(ROOT_TAG).append(">"); return sb.toString(); } @@ -135,6 +211,8 @@ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEven public Map<String, Object> asJSon() { JsonObject jo = new JsonObject(); jo.put("uid", uid); + jo.put("first", first); + jo.put("last", last); if (routeId != null) { jo.put("routeId", routeId); } @@ -144,6 +222,9 @@ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEven if (timestamp > 0) { jo.put("timestamp", timestamp); } + jo.put("elapsed", getElapsed()); + jo.put("done", isDone()); + jo.put("failed", isFailed()); try { // parse back to json object and avoid double message root JsonObject msg = (JsonObject) Jsoner.deserialize(messageAsJSon); @@ -151,6 +232,15 @@ public final class DefaultBacklogTracerEventMessage implements BacklogTracerEven } catch (Exception e) { // ignore } + if (exceptionAsJSon != null) { + try { + // parse back to json object and avoid double message root + JsonObject msg = (JsonObject) Jsoner.deserialize(exceptionAsJSon); + jo.put("exception", msg.get("exception")); + } catch (Exception e) { + // ignore + } + } return jo; } } diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java index d95c803f1d1..715afafd99f 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelInternalProcessor.java @@ -563,7 +563,8 @@ public class CamelInternalProcessor extends DelegateAsyncProcessor implements In /** * Advice to execute the {@link BacklogTracer} if enabled. */ - public static final class BacklogTracerAdvice implements CamelInternalProcessorAdvice, Ordered { + public static final class BacklogTracerAdvice + implements CamelInternalProcessorAdvice<DefaultBacklogTracerEventMessage>, Ordered { private final BacklogTracer backlogTracer; private final NamedNode processorDefinition; @@ -579,7 +580,7 @@ public class CamelInternalProcessor extends DelegateAsyncProcessor implements In } @Override - public Object before(Exchange exchange) throws Exception { + public DefaultBacklogTracerEventMessage before(Exchange exchange) throws Exception { if (backlogTracer.shouldTrace(processorDefinition, exchange)) { long timestamp = System.currentTimeMillis(); String toNode = processorDefinition.getId(); @@ -596,28 +597,72 @@ public class CamelInternalProcessor extends DelegateAsyncProcessor implements In String routeId = routeDefinition != null ? routeDefinition.getRouteId() : null; if (first) { long created = exchange.getCreated(); - DefaultBacklogTracerEventMessage pseudo = new DefaultBacklogTracerEventMessage( - backlogTracer.incrementTraceCounter(), created, routeId, null, exchangeId, messageAsXml, + DefaultBacklogTracerEventMessage pseudoFirst = new DefaultBacklogTracerEventMessage( + true, false, backlogTracer.incrementTraceCounter(), created, routeId, null, exchangeId, + messageAsXml, messageAsJSon); - backlogTracer.traceEvent(pseudo); + backlogTracer.traceEvent(pseudoFirst); + exchange.adapt(ExtendedExchange.class).addOnCompletion(new SynchronizationAdapter() { + @Override + public void onDone(Exchange exchange) { + // create pseudo last + String routeId = routeDefinition != null ? routeDefinition.getRouteId() : null; + String exchangeId = exchange.getExchangeId(); + boolean includeExchangeProperties = backlogTracer.isIncludeExchangeProperties(); + long created = exchange.getCreated(); + String messageAsXml = MessageHelper.dumpAsXml(exchange.getIn(), includeExchangeProperties, true, 4, + true, backlogTracer.isBodyIncludeStreams(), backlogTracer.isBodyIncludeFiles(), + backlogTracer.getBodyMaxChars()); + String messageAsJSon + = MessageHelper.dumpAsJSon(exchange.getIn(), includeExchangeProperties, true, 4, + true, backlogTracer.isBodyIncludeStreams(), backlogTracer.isBodyIncludeFiles(), + backlogTracer.getBodyMaxChars(), true); + DefaultBacklogTracerEventMessage pseudoLast = new DefaultBacklogTracerEventMessage( + false, true, backlogTracer.incrementTraceCounter(), created, routeId, null, exchangeId, + messageAsXml, + messageAsJSon); + backlogTracer.traceEvent(pseudoLast); + doneProcessing(exchange, pseudoLast); + doneProcessing(exchange, pseudoFirst); + } + }); } DefaultBacklogTracerEventMessage event = new DefaultBacklogTracerEventMessage( - backlogTracer.incrementTraceCounter(), timestamp, routeId, toNode, exchangeId, messageAsXml, + false, false, backlogTracer.incrementTraceCounter(), timestamp, routeId, toNode, exchangeId, + messageAsXml, messageAsJSon); backlogTracer.traceEvent(event); + + return event; } return null; } @Override - public void after(Exchange exchange, Object data) throws Exception { - // noop + public void after(Exchange exchange, DefaultBacklogTracerEventMessage data) throws Exception { + doneProcessing(exchange, data); + } + + private void doneProcessing(Exchange exchange, DefaultBacklogTracerEventMessage data) { + if (data != null) { + data.doneProcessing(); + if (!data.isFirst()) { + // we want to capture if there was an exception + Throwable e = exchange.getException(); + if (e != null) { + String xml = MessageHelper.dumpExceptionAsXML(e, 4); + data.setExceptionAsXml(xml); + String json = MessageHelper.dumpExceptionAsJSon(e, 4, true); + data.setExceptionAsJSon(json); + } + } + } } @Override public boolean hasState() { - return false; + return true; } @Override diff --git a/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerFilterTest.java b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerFilterTest.java index dc7d6d034bd..d4c1766b217 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerFilterTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerFilterTest.java @@ -70,7 +70,7 @@ public class BacklogTracerFilterTest extends ManagementTestSupport { = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpAllTracedMessages", null, null); assertNotNull(events); - assertEquals(3, events.size()); + assertEquals(4, events.size()); BacklogTracerEventMessage event = events.get(0); assertEquals(null, event.getToNode()); diff --git a/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerTest.java b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerTest.java index 68a68a0ee74..9d0ee97df6f 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/BacklogTracerTest.java @@ -151,7 +151,11 @@ public class BacklogTracerTest extends ManagementTestSupport { = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpAllTracedMessages", null, null); assertNotNull(events); - assertEquals(6, events.size()); + assertEquals(8, events.size()); + + // first and last events + assertTrue(events.get(0).isFirst()); + assertTrue(events.get(7).isLast()); BacklogTracerEventMessage event0 = events.get(0); assertEquals("route1", event0.getRouteId()); @@ -177,7 +181,7 @@ public class BacklogTracerTest extends ManagementTestSupport { + " </message>", event2.getMessageAsXml()); - BacklogTracerEventMessage event3 = events.get(3); + BacklogTracerEventMessage event3 = events.get(4); assertEquals("route1", event3.getRouteId()); assertEquals(null, event3.getToNode()); assertEquals(" <message exchangeId=\"" + fooExchanges.get(1).getExchangeId() + "\">\n" @@ -185,7 +189,7 @@ public class BacklogTracerTest extends ManagementTestSupport { + " </message>", event3.getMessageAsXml()); - BacklogTracerEventMessage event4 = events.get(4); + BacklogTracerEventMessage event4 = events.get(5); assertEquals("route1", event4.getRouteId()); assertEquals("foo", event4.getToNode()); assertEquals(" <message exchangeId=\"" + fooExchanges.get(1).getExchangeId() + "\">\n" @@ -193,7 +197,7 @@ public class BacklogTracerTest extends ManagementTestSupport { + " </message>", event3.getMessageAsXml()); - BacklogTracerEventMessage event5 = events.get(5); + BacklogTracerEventMessage event5 = events.get(6); assertEquals("route1", event5.getRouteId()); assertEquals("bar", event5.getToNode()); assertEquals(" <message exchangeId=\"" + barExchanges.get(1).getExchangeId() + "\">\n" @@ -231,7 +235,7 @@ public class BacklogTracerTest extends ManagementTestSupport { assertNotNull(dom); NodeList list = dom.getElementsByTagName("backlogTracerEventMessage"); - assertEquals(6, list.getLength()); + assertEquals(8, list.getLength()); } @SuppressWarnings("unchecked") @@ -263,12 +267,12 @@ public class BacklogTracerTest extends ManagementTestSupport { = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpAllTracedMessages", null, null); assertNotNull(events); - assertEquals(6, events.size()); + assertEquals(8, events.size()); // and if we get again they are still there events = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpAllTracedMessages", null, null); assertNotNull(events); - assertEquals(6, events.size()); + assertEquals(8, events.size()); // send in another message resetMocks(); @@ -280,10 +284,10 @@ public class BacklogTracerTest extends ManagementTestSupport { assertMockEndpointsSatisfied(); - // and now we should have 3 more messages + // and now we should have 4 more messages events = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpAllTracedMessages", null, null); assertNotNull(events); - assertEquals(9, events.size()); + assertEquals(12, events.size()); } @SuppressWarnings("unchecked") @@ -380,12 +384,12 @@ public class BacklogTracerTest extends ManagementTestSupport { List<BacklogTracerEventMessage> events = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpTracedMessages", new Object[] { "foo" }, new String[] { "java.lang.String" }); - assertEquals(10, events.size()); + assertEquals(7, events.size()); - // the first should be 0 and the last 9 + // the first should be 3 and the last 9 String xml = events.get(0).getMessageAsXml(); - assertTrue(xml.contains("###0###")); - xml = events.get(9).getMessageAsXml(); + assertTrue(xml.contains("###3###")); + xml = events.get(6).getMessageAsXml(); assertTrue(xml.contains("###9###")); // send in another message @@ -393,12 +397,12 @@ public class BacklogTracerTest extends ManagementTestSupport { events = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpTracedMessages", new Object[] { "foo" }, new String[] { "java.lang.String" }); - assertEquals(10, events.size()); + assertEquals(7, events.size()); // and we are shifted one now xml = events.get(0).getMessageAsXml(); - assertTrue(xml.contains("###1###")); - xml = events.get(9).getMessageAsXml(); + assertTrue(xml.contains("###4###")); + xml = events.get(6).getMessageAsXml(); assertTrue(xml.contains("###10###")); // send in 4 messages @@ -409,12 +413,12 @@ public class BacklogTracerTest extends ManagementTestSupport { events = (List<BacklogTracerEventMessage>) mbeanServer.invoke(on, "dumpTracedMessages", new Object[] { "foo" }, new String[] { "java.lang.String" }); - assertEquals(10, events.size()); + assertEquals(7, events.size()); // and we are shifted +4 now xml = events.get(0).getMessageAsXml(); - assertTrue(xml.contains("###5###")); - xml = events.get(9).getMessageAsXml(); + assertTrue(xml.contains("###8###")); + xml = events.get(6).getMessageAsXml(); assertTrue(xml.contains("###14###")); } diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java index 842ee64ab8b..086719e7c02 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestGetTest.java @@ -65,9 +65,9 @@ public class ManagedFromRestGetTest extends ManagementTestSupport { assertTrue(xml.contains("</rests>")); assertTrue(xml.contains("<param defaultValue=\"1\" dataType=\"integer\" name=\"header_count\"" - + " description=\"header param description1\" type=\"header\" required=\"true\">")); + + " description=\"header param description1\" type=\"header\" required=\"true\">")); assertTrue(xml.contains("<param defaultValue=\"b\" dataType=\"string\" name=\"header_letter\"" - + " description=\"header param description2\" type=\"query\" collectionFormat=\"multi\" required=\"false\">")); + + " description=\"header param description2\" type=\"query\" collectionFormat=\"multi\" required=\"false\">")); assertTrue(xml.contains("<value>1</value>")); assertTrue(xml.contains("<value>a</value>")); diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java index 2f979809d0a..7984b258693 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedFromRestPlaceholderTest.java @@ -66,9 +66,9 @@ public class ManagedFromRestPlaceholderTest extends ManagementTestSupport { assertTrue(xml.contains("</rests>")); assertTrue(xml.contains("<param defaultValue=\"1\" dataType=\"integer\" name=\"header_count\"" - + " description=\"header param description1\" type=\"header\" required=\"true\">")); + + " description=\"header param description1\" type=\"header\" required=\"true\">")); assertTrue(xml.contains("<param defaultValue=\"b\" dataType=\"string\" name=\"header_letter\"" - + " description=\"header param description2\" type=\"query\" collectionFormat=\"multi\" required=\"false\">")); + + " description=\"header param description2\" type=\"query\" collectionFormat=\"multi\" required=\"false\">")); assertTrue(xml.contains("<value>1</value>")); assertTrue(xml.contains("<value>a</value>")); diff --git a/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java index 7f69612a3af..ee85d1603b4 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/MessageHelper.java @@ -20,7 +20,9 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.io.Reader; +import java.io.StringWriter; import java.io.Writer; import java.util.List; import java.util.Map; @@ -854,6 +856,7 @@ public final class MessageHelper { * @param allowFiles whether to include message body if they are file based * @param maxChars clip body after maximum chars (to avoid very big messages). Use 0 or negative * value to not limit at all. + * @param pretty whether to pretty print JSon * @return the JSon */ public static String dumpAsJSon( @@ -952,4 +955,83 @@ public final class MessageHelper { return answer; } + /** + * Dumps the exception as a generic XML structure. + * + * @param indent number of spaces to indent + * @return the XML + */ + public static String dumpExceptionAsXML(Throwable exception, int indent) { + StringBuilder prefix = new StringBuilder(); + for (int i = 0; i < indent; i++) { + prefix.append(" "); + } + + StringBuilder sb = new StringBuilder(); + try { + sb.append(prefix).append("<exception"); + String type = ObjectHelper.classCanonicalName(exception); + if (type != null) { + sb.append(" type=\"").append(type).append("\""); + } + String msg = exception.getMessage(); + if (msg != null) { + msg = StringHelper.xmlEncode(msg); + sb.append(" message=\"").append(msg).append("\""); + } + sb.append(">\n"); + StringWriter sw = new StringWriter(); + exception.printStackTrace(new PrintWriter(sw)); + String trace = sw.toString(); + // must always xml encode + sb.append(StringHelper.xmlEncode(trace)); + sb.append(prefix).append("</exception>"); + } catch (Throwable e) { + // ignore + } + + return sb.toString(); + } + + /** + * Dumps the exception as a generic JSon structure. + * + * @param indent number of spaces to indent + * @param pretty whether to pretty print JSon + * @return the JSon + */ + public static String dumpExceptionAsJSon(Throwable exception, int indent, boolean pretty) { + JsonObject root = new JsonObject(); + JsonObject jo = new JsonObject(); + root.put("exception", jo); + + String type = ObjectHelper.classCanonicalName(exception); + if (type != null) { + jo.put("type", type); + } + String msg = exception.getMessage(); + if (msg != null) { + jo.put("message", type); + } else { + jo.put("message", "[null]"); + } + StringWriter sw = new StringWriter(); + exception.printStackTrace(new PrintWriter(sw)); + String trace = sw.toString(); + try { + jo.put("stackTrace", Jsoner.unescape(trace)); + } catch (Throwable e) { + // ignore as the body is for logging purpose + } + String answer = root.toJson(); + if (pretty) { + if (indent > 0) { + answer = Jsoner.prettyPrint(answer, indent); + } else { + answer = Jsoner.prettyPrint(answer); + } + } + return answer; + } + }
