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}