001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.processor;
018
019import java.io.PrintWriter;
020import java.io.StringWriter;
021import java.util.Map;
022import java.util.TreeMap;
023import java.util.concurrent.Future;
024
025import org.apache.camel.Exchange;
026import org.apache.camel.Message;
027import org.apache.camel.spi.ExchangeFormatter;
028import org.apache.camel.spi.UriParam;
029import org.apache.camel.spi.UriParams;
030import org.apache.camel.util.MessageHelper;
031import org.apache.camel.util.ObjectHelper;
032import org.apache.camel.util.StringHelper;
033
034/**
035 * Default {@link ExchangeFormatter} that have fine grained options to configure what to include in the output.
036 */
037@UriParams
038public class DefaultExchangeFormatter implements ExchangeFormatter {
039
040    protected static final String LS = System.lineSeparator();
041    private static final String SEPARATOR = "###REPLACE_ME###";
042
043    public enum OutputStyle { Default, Tab, Fixed }
044
045    @UriParam(label = "formatting")
046    private boolean showExchangeId;
047    @UriParam(label = "formatting", defaultValue = "true")
048    private boolean showExchangePattern = true;
049    @UriParam(label = "formatting")
050    private boolean showProperties;
051    @UriParam(label = "formatting")
052    private boolean showHeaders;
053    @UriParam(label = "formatting", defaultValue = "true")
054    private boolean skipBodyLineSeparator = true;
055    @UriParam(label = "formatting", defaultValue = "true", description = "Show the message body.")
056    private boolean showBody = true;
057    @UriParam(label = "formatting", defaultValue = "true")
058    private boolean showBodyType = true;
059    @UriParam(label = "formatting")
060    private boolean showOut;
061    @UriParam(label = "formatting")
062    private boolean showException;
063    @UriParam(label = "formatting")
064    private boolean showCaughtException;
065    @UriParam(label = "formatting")
066    private boolean showStackTrace;
067    @UriParam(label = "formatting")
068    private boolean showAll;
069    @UriParam(label = "formatting")
070    private boolean multiline;
071    @UriParam(label = "formatting")
072    private boolean showFuture;
073    @UriParam(label = "formatting")
074    private boolean showStreams;
075    @UriParam(label = "formatting")
076    private boolean showFiles;
077    @UriParam(label = "formatting", defaultValue = "10000")
078    private int maxChars = 10000;
079    @UriParam(label = "formatting", enums = "Default,Tab,Fixed", defaultValue = "Default")
080    private OutputStyle style = OutputStyle.Default;
081
082    private String style(String label) {
083        if (style == OutputStyle.Default) {
084            return String.format(", %s: ", label);
085        } 
086        if (style == OutputStyle.Tab) {
087            return String.format("\t%s: ", label);
088        } else {
089            return String.format("\t%-20s", label);
090        }
091    }
092
093    public String format(Exchange exchange) {
094        Message in = exchange.getIn();
095
096        StringBuilder sb = new StringBuilder();
097        if (showAll || showExchangeId) {
098            if (multiline) {
099                sb.append(SEPARATOR);
100            }
101            sb.append(style("Id")).append(exchange.getExchangeId());
102        }
103        if (showAll || showExchangePattern) {
104            if (multiline) {
105                sb.append(SEPARATOR);
106            }
107            sb.append(style("ExchangePattern")).append(exchange.getPattern());
108        }
109
110        if (showAll || showProperties) {
111            if (multiline) {
112                sb.append(SEPARATOR);
113            }
114            sb.append(style("Properties")).append(sortMap(filterHeaderAndProperties(exchange.getProperties())));
115        }
116        if (showAll || showHeaders) {
117            if (multiline) {
118                sb.append(SEPARATOR);
119            }
120            sb.append(style("Headers")).append(sortMap(filterHeaderAndProperties(in.getHeaders())));
121        }
122        if (showAll || showBodyType) {
123            if (multiline) {
124                sb.append(SEPARATOR);
125            }
126            sb.append(style("BodyType")).append(getBodyTypeAsString(in));
127        }
128        if (showAll || showBody) {
129            if (multiline) {
130                sb.append(SEPARATOR);
131            }
132            String body = getBodyAsString(in);
133            if (skipBodyLineSeparator) {
134                body = StringHelper.replaceAll(body, LS, "");
135            }
136            sb.append(style("Body")).append(body);
137        }
138
139        if (showAll || showException || showCaughtException) {
140
141            // try exception on exchange first
142            Exception exception = exchange.getException();
143            boolean caught = false;
144            if ((showAll || showCaughtException) && exception == null) {
145                // fallback to caught exception
146                exception = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
147                caught = true;
148            }
149
150            if (exception != null) {
151                if (multiline) {
152                    sb.append(SEPARATOR);
153                }
154                if (caught) {
155                    sb.append(style("CaughtExceptionType")).append(exception.getClass().getCanonicalName());
156                    sb.append(style("CaughtExceptionMessage")).append(exception.getMessage());
157                } else {
158                    sb.append(style("ExceptionType")).append(exception.getClass().getCanonicalName());
159                    sb.append(style("ExceptionMessage")).append(exception.getMessage());
160                }
161                if (showAll || showStackTrace) {
162                    StringWriter sw = new StringWriter();
163                    exception.printStackTrace(new PrintWriter(sw));
164                    sb.append(style("StackTrace")).append(sw.toString());
165                }
166            }
167        }
168
169        if (showAll || showOut) {
170            if (exchange.hasOut()) {
171                Message out = exchange.getOut();
172                if (showAll || showHeaders) {
173                    if (multiline) {
174                        sb.append(SEPARATOR);
175                    }
176                    sb.append(style("OutHeaders")).append(sortMap(filterHeaderAndProperties(out.getHeaders())));
177                }
178                if (showAll || showBodyType) {
179                    if (multiline) {
180                        sb.append(SEPARATOR);
181                    }
182                    sb.append(style("OutBodyType")).append(getBodyTypeAsString(out));
183                }
184                if (showAll || showBody) {
185                    if (multiline) {
186                        sb.append(SEPARATOR);
187                    }
188                    String body = getBodyAsString(out);
189                    if (skipBodyLineSeparator) {
190                        body = StringHelper.replaceAll(body, LS, "");
191                    }
192                    sb.append(style("OutBody")).append(body);
193                }
194            } else {
195                if (multiline) {
196                    sb.append(SEPARATOR);
197                }
198                sb.append(style("Out: null"));
199            }
200        }
201
202        if (maxChars > 0) {
203            StringBuilder answer = new StringBuilder();
204            for (String s : sb.toString().split(SEPARATOR)) {
205                if (s != null) {
206                    if (s.length() > maxChars) {
207                        s = s.substring(0, maxChars);
208                        answer.append(s).append("...");
209                    } else {
210                        answer.append(s);
211                    }
212                    if (multiline) {
213                        answer.append(LS);
214                    }
215                }
216            }
217
218            // switch string buffer
219            sb = answer;
220        }
221
222        if (multiline) {
223            sb.insert(0, "Exchange[");
224            sb.append("]");
225            return sb.toString();
226        } else {
227            // get rid of the leading space comma if needed
228            if (sb.length() > 0 && sb.charAt(0) == ',' && sb.charAt(1) == ' ') {
229                sb.replace(0, 2, "");
230            }
231            sb.insert(0, "Exchange[");
232            sb.append("]");
233
234            return sb.toString();
235        }
236    }
237
238    /**
239     * Filters the headers or properties before formatting them. No default behavior, but can be overridden.
240     */
241    protected Map<String, Object> filterHeaderAndProperties(Map<String, Object> map) {
242        return map;
243    }
244
245    public boolean isShowExchangeId() {
246        return showExchangeId;
247    }
248
249    /**
250     * Show the unique exchange ID.
251     */
252    public void setShowExchangeId(boolean showExchangeId) {
253        this.showExchangeId = showExchangeId;
254    }
255
256    public boolean isShowProperties() {
257        return showProperties;
258    }
259
260    /**
261     * Show the exchange properties.
262     */
263    public void setShowProperties(boolean showProperties) {
264        this.showProperties = showProperties;
265    }
266
267    public boolean isShowHeaders() {
268        return showHeaders;
269    }
270
271    /**
272     * Show the message headers.
273     */
274    public void setShowHeaders(boolean showHeaders) {
275        this.showHeaders = showHeaders;
276    }
277
278    public boolean isSkipBodyLineSeparator() {
279        return skipBodyLineSeparator;
280    }
281
282    /**
283     * Whether to skip line separators when logging the message body.
284     * This allows to log the message body in one line, setting this option to false will preserve any line separators
285     * from the body, which then will log the body as is.
286     */
287    public void setSkipBodyLineSeparator(boolean skipBodyLineSeparator) {
288        this.skipBodyLineSeparator = skipBodyLineSeparator;
289    }
290
291    public boolean isShowBodyType() {
292        return showBodyType;
293    }
294
295    /**
296     * Show the body Java type.
297     */
298    public void setShowBodyType(boolean showBodyType) {
299        this.showBodyType = showBodyType;
300    }
301
302    public boolean isShowBody() {
303        return showBody;
304    }
305
306    /*
307     * Show the message body.
308     */
309    public void setShowBody(boolean showBody) {
310        this.showBody = showBody;
311    }
312
313    public boolean isShowOut() {
314        return showOut;
315    }
316
317    /**
318     * If the exchange has an out message, show the out message.
319     */
320    public void setShowOut(boolean showOut) {
321        this.showOut = showOut;
322    }
323
324    public boolean isShowAll() {
325        return showAll;
326    }
327
328    /**
329     * Quick option for turning all options on. (multiline, maxChars has to be manually set if to be used)
330     */
331    public void setShowAll(boolean showAll) {
332        this.showAll = showAll;
333    }
334
335    public boolean isShowException() {
336        return showException;
337    }
338
339    /**
340     * If the exchange has an exception, show the exception message (no stacktrace)
341     */
342    public void setShowException(boolean showException) {
343        this.showException = showException;
344    }
345
346    public boolean isShowStackTrace() {
347        return showStackTrace;
348    }
349
350    /**
351     * Show the stack trace, if an exchange has an exception. Only effective if one of showAll, showException or showCaughtException are enabled.
352     */
353    public void setShowStackTrace(boolean showStackTrace) {
354        this.showStackTrace = showStackTrace;
355    }
356
357    public boolean isShowCaughtException() {
358        return showCaughtException;
359    }
360
361    /**
362     * f the exchange has a caught exception, show the exception message (no stack trace).
363     * A caught exception is stored as a property on the exchange (using the key {@link org.apache.camel.Exchange#EXCEPTION_CAUGHT}
364     * and for instance a doCatch can catch exceptions.
365     */
366    public void setShowCaughtException(boolean showCaughtException) {
367        this.showCaughtException = showCaughtException;
368    }
369
370    public boolean isMultiline() {
371        return multiline;
372    }
373
374    public int getMaxChars() {
375        return maxChars;
376    }
377
378    /**
379     * Limits the number of characters logged per line.
380     */
381    public void setMaxChars(int maxChars) {
382        this.maxChars = maxChars;
383    }
384
385    /**
386     * If enabled then each information is outputted on a newline.
387     */
388    public void setMultiline(boolean multiline) {
389        this.multiline = multiline;
390    }
391
392    public boolean isShowFuture() {
393        return showFuture;
394    }
395
396    /**
397     * If enabled Camel will on Future objects wait for it to complete to obtain the payload to be logged.
398     */
399    public void setShowFuture(boolean showFuture) {
400        this.showFuture = showFuture;
401    }
402
403    public boolean isShowExchangePattern() {
404        return showExchangePattern;
405    }
406
407    /**
408     * Shows the Message Exchange Pattern (or MEP for short).
409     */
410    public void setShowExchangePattern(boolean showExchangePattern) {
411        this.showExchangePattern = showExchangePattern;
412    }
413
414    public boolean isShowStreams() {
415        return showStreams;
416    }
417
418    /**
419     * Whether Camel should show stream bodies or not (eg such as java.io.InputStream).
420     * Beware if you enable this option then you may not be able later to access the message body
421     * as the stream have already been read by this logger.
422     * To remedy this you will have to use Stream Caching.
423     */
424    public void setShowStreams(boolean showStreams) {
425        this.showStreams = showStreams;
426    }
427
428    public boolean isShowFiles() {
429        return showFiles;
430    }
431
432    /**
433     * If enabled Camel will output files
434     */
435    public void setShowFiles(boolean showFiles) {
436        this.showFiles = showFiles;
437    }
438
439    public OutputStyle getStyle() {
440        return style;
441    }
442
443    /**
444     * Sets the outputs style to use.
445     */
446    public void setStyle(OutputStyle style) {
447        this.style = style;
448    }
449
450    // Implementation methods
451    //-------------------------------------------------------------------------
452    protected String getBodyAsString(Message message) {
453        if (message.getBody() instanceof Future) {
454            if (!isShowFuture()) {
455                // just use a to string of the future object
456                return message.getBody().toString();
457            }
458        }
459
460        return MessageHelper.extractBodyForLogging(message, "", isShowStreams(), isShowFiles(), getMaxChars(message));
461    }
462
463    private int getMaxChars(Message message) {
464        int maxChars = getMaxChars();
465        if (message.getExchange() != null) {
466            String globalOption = message.getExchange().getContext().getGlobalOption(Exchange.LOG_DEBUG_BODY_MAX_CHARS);
467            if (globalOption != null) {
468                maxChars = message.getExchange().getContext().getTypeConverter().convertTo(Integer.class, globalOption);
469            }
470        }
471        return maxChars;
472    }
473
474    protected String getBodyTypeAsString(Message message) {
475        String answer = ObjectHelper.classCanonicalName(message.getBody());
476        if (answer != null && answer.startsWith("java.lang.")) {
477            return answer.substring(10);
478        }
479        return answer;
480    }
481
482    private static Map<String, Object> sortMap(Map<String, Object> map) {
483        Map<String, Object> answer = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
484        answer.putAll(map);
485        return answer;
486    }
487
488}