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(exchange.getProperties())); 115 } 116 if (showAll || showHeaders) { 117 if (multiline) { 118 sb.append(SEPARATOR); 119 } 120 sb.append(style("Headers")).append(sortMap(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(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 public boolean isShowExchangeId() { 239 return showExchangeId; 240 } 241 242 /** 243 * Show the unique exchange ID. 244 */ 245 public void setShowExchangeId(boolean showExchangeId) { 246 this.showExchangeId = showExchangeId; 247 } 248 249 public boolean isShowProperties() { 250 return showProperties; 251 } 252 253 /** 254 * Show the exchange properties. 255 */ 256 public void setShowProperties(boolean showProperties) { 257 this.showProperties = showProperties; 258 } 259 260 public boolean isShowHeaders() { 261 return showHeaders; 262 } 263 264 /** 265 * Show the message headers. 266 */ 267 public void setShowHeaders(boolean showHeaders) { 268 this.showHeaders = showHeaders; 269 } 270 271 public boolean isSkipBodyLineSeparator() { 272 return skipBodyLineSeparator; 273 } 274 275 /** 276 * Whether to skip line separators when logging the message body. 277 * This allows to log the message body in one line, setting this option to false will preserve any line separators 278 * from the body, which then will log the body as is. 279 */ 280 public void setSkipBodyLineSeparator(boolean skipBodyLineSeparator) { 281 this.skipBodyLineSeparator = skipBodyLineSeparator; 282 } 283 284 public boolean isShowBodyType() { 285 return showBodyType; 286 } 287 288 /** 289 * Show the body Java type. 290 */ 291 public void setShowBodyType(boolean showBodyType) { 292 this.showBodyType = showBodyType; 293 } 294 295 public boolean isShowBody() { 296 return showBody; 297 } 298 299 /* 300 * Show the message body. 301 */ 302 public void setShowBody(boolean showBody) { 303 this.showBody = showBody; 304 } 305 306 public boolean isShowOut() { 307 return showOut; 308 } 309 310 /** 311 * If the exchange has an out message, show the out message. 312 */ 313 public void setShowOut(boolean showOut) { 314 this.showOut = showOut; 315 } 316 317 public boolean isShowAll() { 318 return showAll; 319 } 320 321 /** 322 * Quick option for turning all options on. (multiline, maxChars has to be manually set if to be used) 323 */ 324 public void setShowAll(boolean showAll) { 325 this.showAll = showAll; 326 } 327 328 public boolean isShowException() { 329 return showException; 330 } 331 332 /** 333 * If the exchange has an exception, show the exception message (no stacktrace) 334 */ 335 public void setShowException(boolean showException) { 336 this.showException = showException; 337 } 338 339 public boolean isShowStackTrace() { 340 return showStackTrace; 341 } 342 343 /** 344 * Show the stack trace, if an exchange has an exception. Only effective if one of showAll, showException or showCaughtException are enabled. 345 */ 346 public void setShowStackTrace(boolean showStackTrace) { 347 this.showStackTrace = showStackTrace; 348 } 349 350 public boolean isShowCaughtException() { 351 return showCaughtException; 352 } 353 354 /** 355 * f the exchange has a caught exception, show the exception message (no stack trace). 356 * A caught exception is stored as a property on the exchange (using the key {@link org.apache.camel.Exchange#EXCEPTION_CAUGHT} 357 * and for instance a doCatch can catch exceptions. 358 */ 359 public void setShowCaughtException(boolean showCaughtException) { 360 this.showCaughtException = showCaughtException; 361 } 362 363 public boolean isMultiline() { 364 return multiline; 365 } 366 367 public int getMaxChars() { 368 return maxChars; 369 } 370 371 /** 372 * Limits the number of characters logged per line. 373 */ 374 public void setMaxChars(int maxChars) { 375 this.maxChars = maxChars; 376 } 377 378 /** 379 * If enabled then each information is outputted on a newline. 380 */ 381 public void setMultiline(boolean multiline) { 382 this.multiline = multiline; 383 } 384 385 public boolean isShowFuture() { 386 return showFuture; 387 } 388 389 /** 390 * If enabled Camel will on Future objects wait for it to complete to obtain the payload to be logged. 391 */ 392 public void setShowFuture(boolean showFuture) { 393 this.showFuture = showFuture; 394 } 395 396 public boolean isShowExchangePattern() { 397 return showExchangePattern; 398 } 399 400 /** 401 * Shows the Message Exchange Pattern (or MEP for short). 402 */ 403 public void setShowExchangePattern(boolean showExchangePattern) { 404 this.showExchangePattern = showExchangePattern; 405 } 406 407 public boolean isShowStreams() { 408 return showStreams; 409 } 410 411 /** 412 * Whether Camel should show stream bodies or not (eg such as java.io.InputStream). 413 * Beware if you enable this option then you may not be able later to access the message body 414 * as the stream have already been read by this logger. 415 * To remedy this you will have to use Stream Caching. 416 */ 417 public void setShowStreams(boolean showStreams) { 418 this.showStreams = showStreams; 419 } 420 421 public boolean isShowFiles() { 422 return showFiles; 423 } 424 425 /** 426 * If enabled Camel will output files 427 */ 428 public void setShowFiles(boolean showFiles) { 429 this.showFiles = showFiles; 430 } 431 432 public OutputStyle getStyle() { 433 return style; 434 } 435 436 /** 437 * Sets the outputs style to use. 438 */ 439 public void setStyle(OutputStyle style) { 440 this.style = style; 441 } 442 443 // Implementation methods 444 //------------------------------------------------------------------------- 445 protected String getBodyAsString(Message message) { 446 if (message.getBody() instanceof Future) { 447 if (!isShowFuture()) { 448 // just use a to string of the future object 449 return message.getBody().toString(); 450 } 451 } 452 453 return MessageHelper.extractBodyForLogging(message, "", isShowStreams(), isShowFiles(), getMaxChars(message)); 454 } 455 456 private int getMaxChars(Message message) { 457 int maxChars = getMaxChars(); 458 if (message.getExchange() != null) { 459 String globalOption = message.getExchange().getContext().getGlobalOption(Exchange.LOG_DEBUG_BODY_MAX_CHARS); 460 if (globalOption != null) { 461 maxChars = message.getExchange().getContext().getTypeConverter().convertTo(Integer.class, globalOption); 462 } 463 } 464 return maxChars; 465 } 466 467 protected String getBodyTypeAsString(Message message) { 468 String answer = ObjectHelper.classCanonicalName(message.getBody()); 469 if (answer != null && answer.startsWith("java.lang.")) { 470 return answer.substring(10); 471 } 472 return answer; 473 } 474 475 private static Map<String, Object> sortMap(Map<String, Object> map) { 476 Map<String, Object> answer = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 477 answer.putAll(map); 478 return answer; 479 } 480 481}