/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.processor; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.Future; import org.apache.camel.Exchange; import org.apache.camel.Message; import org.apache.camel.spi.ExchangeFormatter; import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriParams; import org.apache.camel.util.MessageHelper; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.StringHelper; /** * Default {@link ExchangeFormatter} that have fine grained options to configure what to include in the output. */ @UriParams public class DefaultExchangeFormatter implements ExchangeFormatter { protected static final String LS = System.getProperty("line.separator"); private static final String SEPARATOR = "###REPLACE_ME###"; public enum OutputStyle { Default, Tab, Fixed } @UriParam(label = "formatting") private boolean showExchangeId; @UriParam(label = "formatting", defaultValue = "true") private boolean showExchangePattern = true; @UriParam(label = "formatting") private boolean showProperties; @UriParam(label = "formatting") private boolean showHeaders; @UriParam(label = "formatting", defaultValue = "true") private boolean skipBodyLineSeparator = true; @UriParam(label = "formatting", defaultValue = "true", description = "Show the message body.") private boolean showBody = true; @UriParam(label = "formatting", defaultValue = "true") private boolean showBodyType = true; @UriParam(label = "formatting") private boolean showOut; @UriParam(label = "formatting") private boolean showException; @UriParam(label = "formatting") private boolean showCaughtException; @UriParam(label = "formatting") private boolean showStackTrace; @UriParam(label = "formatting") private boolean showAll; @UriParam(label = "formatting") private boolean multiline; @UriParam(label = "formatting") private boolean showFuture; @UriParam(label = "formatting") private boolean showStreams; @UriParam(label = "formatting") private boolean showFiles; @UriParam(label = "formatting", defaultValue = "10000") private int maxChars = 10000; @UriParam(label = "formatting", enums = "Default,Tab,Fixed", defaultValue = "Default") private OutputStyle style = OutputStyle.Default; private String style(String label) { if (style == OutputStyle.Default) { return String.format(", %s: ", label); } if (style == OutputStyle.Tab) { return String.format("\t%s: ", label); } else { return String.format("\t%-20s", label); } } public String format(Exchange exchange) { Message in = exchange.getIn(); StringBuilder sb = new StringBuilder(); if (showAll || showExchangeId) { if (multiline) { sb.append(SEPARATOR); } sb.append(style("Id")).append(exchange.getExchangeId()); } if (showAll || showExchangePattern) { if (multiline) { sb.append(SEPARATOR); } sb.append(style("ExchangePattern")).append(exchange.getPattern()); } if (showAll || showProperties) { if (multiline) { sb.append(SEPARATOR); } sb.append(style("Properties")).append(sortMap(exchange.getProperties())); } if (showAll || showHeaders) { if (multiline) { sb.append(SEPARATOR); } sb.append(style("Headers")).append(sortMap(in.getHeaders())); } if (showAll || showBodyType) { if (multiline) { sb.append(SEPARATOR); } sb.append(style("BodyType")).append(getBodyTypeAsString(in)); } if (showAll || showBody) { if (multiline) { sb.append(SEPARATOR); } String body = getBodyAsString(in); if (skipBodyLineSeparator) { body = StringHelper.replaceAll(body, LS, ""); } sb.append(style("Body")).append(body); } if (showAll || showException || showCaughtException) { // try exception on exchange first Exception exception = exchange.getException(); boolean caught = false; if ((showAll || showCaughtException) && exception == null) { // fallback to caught exception exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class); caught = true; } if (exception != null) { if (multiline) { sb.append(SEPARATOR); } if (caught) { sb.append(style("CaughtExceptionType")).append(exception.getClass().getCanonicalName()); sb.append(style("CaughtExceptionMessage")).append(exception.getMessage()); } else { sb.append(style("ExceptionType")).append(exception.getClass().getCanonicalName()); sb.append(style("ExceptionMessage")).append(exception.getMessage()); } if (showAll || showStackTrace) { StringWriter sw = new StringWriter(); exception.printStackTrace(new PrintWriter(sw)); sb.append(style("StackTrace")).append(sw.toString()); } } } if (showAll || showOut) { if (exchange.hasOut()) { Message out = exchange.getOut(); if (showAll || showHeaders) { if (multiline) { sb.append(SEPARATOR); } sb.append(style("OutHeaders")).append(sortMap(out.getHeaders())); } if (showAll || showBodyType) { if (multiline) { sb.append(SEPARATOR); } sb.append(style("OutBodyType")).append(getBodyTypeAsString(out)); } if (showAll || showBody) { if (multiline) { sb.append(SEPARATOR); } String body = getBodyAsString(out); if (skipBodyLineSeparator) { body = StringHelper.replaceAll(body, LS, ""); } sb.append(style("OutBody")).append(body); } } else { if (multiline) { sb.append(SEPARATOR); } sb.append(style("Out: null")); } } if (maxChars > 0) { StringBuilder answer = new StringBuilder(); for (String s : sb.toString().split(SEPARATOR)) { if (s != null) { if (s.length() > maxChars) { s = s.substring(0, maxChars); answer.append(s).append("..."); } else { answer.append(s); } if (multiline) { answer.append(LS); } } } // switch string buffer sb = answer; } if (multiline) { sb.insert(0, "Exchange["); sb.append("]"); return sb.toString(); } else { // get rid of the leading space comma if needed if (sb.length() > 0 && sb.charAt(0) == ',' && sb.charAt(1) == ' ') { sb.replace(0, 2, ""); } sb.insert(0, "Exchange["); sb.append("]"); return sb.toString(); } } public boolean isShowExchangeId() { return showExchangeId; } /** * Show the unique exchange ID. */ public void setShowExchangeId(boolean showExchangeId) { this.showExchangeId = showExchangeId; } public boolean isShowProperties() { return showProperties; } /** * Show the exchange properties. */ public void setShowProperties(boolean showProperties) { this.showProperties = showProperties; } public boolean isShowHeaders() { return showHeaders; } /** * Show the message headers. */ public void setShowHeaders(boolean showHeaders) { this.showHeaders = showHeaders; } public boolean isSkipBodyLineSeparator() { return skipBodyLineSeparator; } /** * Whether to skip line separators when logging the message body. * This allows to log the message body in one line, setting this option to false will preserve any line separators * from the body, which then will log the body as is. */ public void setSkipBodyLineSeparator(boolean skipBodyLineSeparator) { this.skipBodyLineSeparator = skipBodyLineSeparator; } public boolean isShowBodyType() { return showBodyType; } /** * Show the body Java type. */ public void setShowBodyType(boolean showBodyType) { this.showBodyType = showBodyType; } public boolean isShowBody() { return showBody; } /* * Show the message body. */ public void setShowBody(boolean showBody) { this.showBody = showBody; } public boolean isShowOut() { return showOut; } /** * If the exchange has an out message, show the out message. */ public void setShowOut(boolean showOut) { this.showOut = showOut; } public boolean isShowAll() { return showAll; } /** * Quick option for turning all options on. (multiline, maxChars has to be manually set if to be used) */ public void setShowAll(boolean showAll) { this.showAll = showAll; } public boolean isShowException() { return showException; } /** * If the exchange has an exception, show the exception message (no stacktrace) */ public void setShowException(boolean showException) { this.showException = showException; } public boolean isShowStackTrace() { return showStackTrace; } /** * Show the stack trace, if an exchange has an exception. Only effective if one of showAll, showException or showCaughtException are enabled. */ public void setShowStackTrace(boolean showStackTrace) { this.showStackTrace = showStackTrace; } public boolean isShowCaughtException() { return showCaughtException; } /** * f the exchange has a caught exception, show the exception message (no stack trace). * A caught exception is stored as a property on the exchange (using the key {@link org.apache.camel.Exchange#EXCEPTION_CAUGHT} * and for instance a doCatch can catch exceptions. */ public void setShowCaughtException(boolean showCaughtException) { this.showCaughtException = showCaughtException; } public boolean isMultiline() { return multiline; } public int getMaxChars() { return maxChars; } /** * Limits the number of characters logged per line. */ public void setMaxChars(int maxChars) { this.maxChars = maxChars; } /** * If enabled then each information is outputted on a newline. */ public void setMultiline(boolean multiline) { this.multiline = multiline; } public boolean isShowFuture() { return showFuture; } /** * If enabled Camel will on Future objects wait for it to complete to obtain the payload to be logged. */ public void setShowFuture(boolean showFuture) { this.showFuture = showFuture; } public boolean isShowExchangePattern() { return showExchangePattern; } /** * Shows the Message Exchange Pattern (or MEP for short). */ public void setShowExchangePattern(boolean showExchangePattern) { this.showExchangePattern = showExchangePattern; } public boolean isShowStreams() { return showStreams; } /** * Whether Camel should show stream bodies or not (eg such as java.io.InputStream). * Beware if you enable this option then you may not be able later to access the message body * as the stream have already been read by this logger. * To remedy this you will have to use Stream Caching. */ public void setShowStreams(boolean showStreams) { this.showStreams = showStreams; } public boolean isShowFiles() { return showFiles; } /** * If enabled Camel will output files */ public void setShowFiles(boolean showFiles) { this.showFiles = showFiles; } public OutputStyle getStyle() { return style; } /** * Sets the outputs style to use. */ public void setStyle(OutputStyle style) { this.style = style; } // Implementation methods //------------------------------------------------------------------------- protected String getBodyAsString(Message message) { if (message.getBody() instanceof Future) { if (!isShowFuture()) { // just use a to string of the future object return message.getBody().toString(); } } return MessageHelper.extractBodyForLogging(message, "", isShowStreams(), isShowFiles(), getMaxChars(message)); } private int getMaxChars(Message message) { int maxChars = getMaxChars(); if (message.getExchange() != null) { String globalOption = message.getExchange().getContext().getGlobalOption(Exchange.LOG_DEBUG_BODY_MAX_CHARS); if (globalOption != null) { maxChars = message.getExchange().getContext().getTypeConverter().convertTo(Integer.class, globalOption); } } return maxChars; } protected String getBodyTypeAsString(Message message) { String answer = ObjectHelper.classCanonicalName(message.getBody()); if (answer != null && answer.startsWith("java.lang.")) { return answer.substring(10); } return answer; } private static Map<String, Object> sortMap(Map<String, Object> map) { Map<String, Object> answer = new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER); answer.putAll(map); return answer; } }