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 */ 017 package org.apache.camel.processor.interceptor; 018 019 import java.util.Date; 020 import java.util.HashMap; 021 import java.util.Map; 022 023 import org.apache.camel.Endpoint; 024 import org.apache.camel.Exchange; 025 import org.apache.camel.Expression; 026 import org.apache.camel.Processor; 027 import org.apache.camel.Producer; 028 import org.apache.camel.impl.DefaultExchange; 029 import org.apache.camel.impl.DefaultRouteNode; 030 import org.apache.camel.model.InterceptDefinition; 031 import org.apache.camel.model.OnCompletionDefinition; 032 import org.apache.camel.model.OnExceptionDefinition; 033 import org.apache.camel.model.ProcessorDefinition; 034 import org.apache.camel.processor.DelegateProcessor; 035 import org.apache.camel.processor.Logger; 036 import org.apache.camel.spi.ExchangeFormatter; 037 import org.apache.camel.spi.InterceptStrategy; 038 import org.apache.camel.spi.TraceableUnitOfWork; 039 import org.apache.camel.util.IntrospectionSupport; 040 import org.apache.camel.util.ObjectHelper; 041 import org.apache.camel.util.ServiceHelper; 042 import org.apache.commons.logging.Log; 043 import org.apache.commons.logging.LogFactory; 044 045 /** 046 * An interceptor for debugging and tracing routes 047 * 048 * @version $Revision: 795369 $ 049 */ 050 public class TraceInterceptor extends DelegateProcessor implements ExchangeFormatter { 051 private static final transient Log LOG = LogFactory.getLog(TraceInterceptor.class); 052 private static final String JPA_TRACE_EVENT_MESSAGE = "org.apache.camel.processor.interceptor.JpaTraceEventMessage"; 053 private Logger logger; 054 private Producer traceEventProducer; 055 private final ProcessorDefinition node; 056 private final Tracer tracer; 057 private TraceFormatter formatter; 058 private Class jpaTraceEventMessageClass; 059 060 public TraceInterceptor(ProcessorDefinition node, Processor target, TraceFormatter formatter, Tracer tracer) { 061 super(target); 062 this.tracer = tracer; 063 this.node = node; 064 this.formatter = formatter; 065 066 // set logger to use 067 if (tracer.getLogName() != null) { 068 logger = new Logger(LogFactory.getLog(tracer.getLogName()), this); 069 } else { 070 // use default logger 071 logger = new Logger(LogFactory.getLog(TraceInterceptor.class), this); 072 } 073 074 // set logging level if provided 075 if (tracer.getLogLevel() != null) { 076 logger.setLevel(tracer.getLogLevel()); 077 } 078 079 if (tracer.getFormatter() != null) { 080 this.formatter = tracer.getFormatter(); 081 } 082 } 083 084 public TraceInterceptor(ProcessorDefinition node, Processor target, Tracer tracer) { 085 this(node, target, null, tracer); 086 } 087 088 @Override 089 public String toString() { 090 return "TraceInterceptor[" + node + "]"; 091 } 092 093 public void process(final Exchange exchange) throws Exception { 094 // interceptor will also trace routes supposed only for TraceEvents so we need to skip 095 // logging TraceEvents to avoid infinite looping 096 if (exchange.getProperty(Exchange.TRACE_EVENT, Boolean.class) != null) { 097 // but we must still process to allow routing of TraceEvents to eg a JPA endpoint 098 super.process(exchange); 099 return; 100 } 101 102 boolean shouldLog = shouldLogNode(node) && shouldLogExchange(exchange); 103 104 // whether we should trace it or not, some nodes should be skipped as they are abstract 105 // intermedidate steps for instance related to on completion 106 boolean trace = true; 107 108 // okay this is a regular exchange being routed we might need to log and trace 109 try { 110 // before 111 if (shouldLog) { 112 113 // register route path taken if TraceableUnitOfWork unit of work 114 if (exchange.getUnitOfWork() instanceof TraceableUnitOfWork) { 115 TraceableUnitOfWork tuow = (TraceableUnitOfWork) exchange.getUnitOfWork(); 116 117 if (node instanceof OnExceptionDefinition) { 118 // special for on exception so we can see it in the trace logs 119 trace = beforeOnException((OnExceptionDefinition) node, tuow, exchange); 120 } else if (node instanceof OnCompletionDefinition) { 121 // special for on completion so we can see it in the trace logs 122 trace = beforeOnCompletion((OnCompletionDefinition) node, tuow, exchange); 123 } else { 124 // regular so just add it 125 tuow.addTraced(new DefaultRouteNode(node, super.getProcessor())); 126 } 127 } 128 } 129 130 // log and trace the processor 131 if (trace) { 132 logExchange(exchange); 133 traceExchange(exchange); 134 } 135 136 // some nodes need extra work to trace it 137 if (exchange.getUnitOfWork() instanceof TraceableUnitOfWork) { 138 TraceableUnitOfWork tuow = (TraceableUnitOfWork) exchange.getUnitOfWork(); 139 140 if (node instanceof InterceptDefinition) { 141 // special for intercept() as we would like to trace the processor that was intercepted 142 // as well, otherwise we only see the intercepted route, but we need the both to be logged/traced 143 afterIntercept((InterceptDefinition) node, tuow, exchange); 144 } 145 } 146 147 // process the exchange 148 super.proceed(exchange); 149 150 // after (trace out) 151 if (shouldLog && tracer.isTraceOutExchanges()) { 152 logExchange(exchange); 153 traceExchange(exchange); 154 } 155 } catch (Exception e) { 156 if (shouldLogException(exchange)) { 157 logException(exchange, e); 158 } 159 throw e; 160 } 161 } 162 163 public Object format(Exchange exchange) { 164 return formatter.format(this, this.getNode(), exchange); 165 } 166 167 // Properties 168 //------------------------------------------------------------------------- 169 public ProcessorDefinition getNode() { 170 return node; 171 } 172 173 public Logger getLogger() { 174 return logger; 175 } 176 177 public TraceFormatter getFormatter() { 178 return formatter; 179 } 180 181 182 // Implementation methods 183 //------------------------------------------------------------------------- 184 protected boolean beforeOnException(OnExceptionDefinition onException, TraceableUnitOfWork tuow, Exchange exchange) throws Exception { 185 // lets see if this is the first time for this exception 186 int index = tuow.getAndIncrement(node); 187 if (index == 0) { 188 class OnExceptionExpression implements Expression { 189 @SuppressWarnings("unchecked") 190 public Object evaluate(Exchange exchange, Class type) { 191 String label = "OnException"; 192 if (exchange.getProperty(Exchange.EXCEPTION_CAUGHT) != null) { 193 label += "[" + exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class).getClass().getSimpleName() + "]"; 194 } 195 return exchange.getContext().getTypeConverter().convertTo(type, label); 196 } 197 198 } 199 // yes its first time then do some special to log and trace the 200 // start of onException 201 Expression exp = new OnExceptionExpression(); 202 203 // add our pesudo node 204 tuow.addTraced(new DefaultRouteNode(node, exp)); 205 206 // log and trace the processor that was onException so we can see immediately 207 logExchange(exchange); 208 traceExchange(exchange); 209 } 210 211 // add the processor that is invoked for this onException 212 tuow.addTraced(new DefaultRouteNode(node, super.getProcessor())); 213 return true; 214 } 215 216 217 protected boolean beforeOnCompletion(OnCompletionDefinition onCompletion, TraceableUnitOfWork tuow, Exchange exchange) throws Exception { 218 // we should only trace when we do the actual onCompletion 219 // the problem is that onCompletion is added at the very beginning of a route to be able to 220 // add synchronization hoos on unit of work so it knows to invoke the onCompletion when the 221 // exchange is done. But in the trace log we want to defer the onCompletion being logged 222 // unitl the exchange is actually completed and is doing the onCompletion routing 223 // so if the last node is null then we have just started and thus should not trace this node 224 boolean answer = tuow.getLastNode() != null; 225 226 if (exchange.getProperty(Exchange.ON_COMPLETION) != null) { 227 // if ON_COMPLETION is not null then we are actually doing the onCompletion routing 228 229 // we should trace the onCompletion route and we want a start log of the onCompletion 230 // step so get the index and see if its 0 then we can add our speical log 231 int index = tuow.getAndIncrement(node); 232 if (index == 0) { 233 class OnCompletionExpression implements Expression { 234 @SuppressWarnings("unchecked") 235 public Object evaluate(Exchange exchange, Class type) { 236 String label = "OnCompletion[" + exchange.getProperty(Exchange.CORRELATION_ID) + "]"; 237 return exchange.getContext().getTypeConverter().convertTo(type, label); 238 } 239 } 240 // yes its first time then do some special to log and trace the start of onCompletion 241 Expression exp = new OnCompletionExpression(); 242 // add the onCompletion and then the processor that is invoked nest 243 tuow.addTraced(new DefaultRouteNode(node, exp)); 244 tuow.addTraced(new DefaultRouteNode(node, super.getProcessor())); 245 246 // log and trace so we get the onCompletion -> processor in the log 247 logExchange(exchange); 248 traceExchange(exchange); 249 } else { 250 // we are doing the onCompletion but this is after the start so just 251 // add the processor and do no special start message 252 tuow.addTraced(new DefaultRouteNode(node, super.getProcessor())); 253 } 254 255 } 256 257 return answer; 258 } 259 260 protected boolean afterIntercept(InterceptDefinition interceptr, TraceableUnitOfWork tuow, Exchange exchange) throws Exception { 261 // get the intercepted processor from the definition 262 // we need to use the UoW to have its own index of how far we got into the list 263 // of intercepted processors the intercept definition holds as the intercept 264 // definition is a single object that is shared by concurrent thread being routed 265 // so each exchange has its own private counter 266 InterceptDefinition intercept = (InterceptDefinition) node; 267 Processor last = intercept.getInterceptedProcessor(tuow.getAndIncrement(intercept)); 268 if (last != null) { 269 tuow.addTraced(new DefaultRouteNode(node, last)); 270 271 // log and trace the processor that was intercepted so we can see it 272 logExchange(exchange); 273 traceExchange(exchange); 274 } 275 276 return true; 277 } 278 279 protected void logExchange(Exchange exchange) { 280 // process the exchange that formats and logs it 281 logger.process(exchange); 282 } 283 284 @SuppressWarnings("unchecked") 285 protected void traceExchange(Exchange exchange) throws Exception { 286 // should we send a trace event to an optional destination? 287 if (tracer.getDestination() != null || tracer.getDestinationUri() != null) { 288 289 // create event exchange and add event information 290 Date timestamp = new Date(); 291 Exchange event = new DefaultExchange(exchange); 292 event.setProperty(Exchange.TRACE_EVENT_NODE_ID, node.getId()); 293 event.setProperty(Exchange.TRACE_EVENT_TIMESTAMP, timestamp); 294 // keep a reference to the original exchange in case its needed 295 event.setProperty(Exchange.TRACE_EVENT_EXCHANGE, exchange); 296 297 // create event message to sent as in body containing event information such as 298 // from node, to node, etc. 299 TraceEventMessage msg = new DefaultTraceEventMessage(timestamp, node, exchange); 300 301 // should we use ordinary or jpa objects 302 if (tracer.isUseJpa()) { 303 LOG.trace("Using class: " + JPA_TRACE_EVENT_MESSAGE + " for tracing event messages"); 304 305 // load the jpa event message class 306 loadJpaTraceEventMessageClass(exchange); 307 // create a new instance of the event message class 308 Object jpa = ObjectHelper.newInstance(jpaTraceEventMessageClass); 309 310 // copy options from event to jpa 311 Map options = new HashMap(); 312 IntrospectionSupport.getProperties(msg, options, null); 313 IntrospectionSupport.setProperties(jpa, options); 314 // and set the timestamp as its not a String type 315 IntrospectionSupport.setProperty(jpa, "timestamp", msg.getTimestamp()); 316 317 event.getIn().setBody(jpa); 318 } else { 319 event.getIn().setBody(msg); 320 } 321 322 // marker property to indicate its a tracing event being routed in case 323 // new Exchange instances is created during trace routing so we can check 324 // for this marker when interceptor also kickins in during routing of trace events 325 event.setProperty(Exchange.TRACE_EVENT, Boolean.TRUE); 326 try { 327 // process the trace route 328 getTraceEventProducer(exchange).process(event); 329 } catch (Exception e) { 330 // log and ignore this as the original Exchange should be allowed to continue 331 LOG.error("Error processing trace event (original Exchange will continue): " + event, e); 332 } 333 } 334 } 335 336 private synchronized void loadJpaTraceEventMessageClass(Exchange exchange) { 337 if (jpaTraceEventMessageClass == null) { 338 jpaTraceEventMessageClass = exchange.getContext().getClassResolver().resolveClass(JPA_TRACE_EVENT_MESSAGE); 339 if (jpaTraceEventMessageClass == null) { 340 throw new IllegalArgumentException("Cannot find class: " + JPA_TRACE_EVENT_MESSAGE 341 + ". Make sure camel-jpa.jar is in the classpath."); 342 } 343 } 344 } 345 346 protected void logException(Exchange exchange, Throwable throwable) { 347 if (tracer.isTraceExceptions()) { 348 if (tracer.isLogStackTrace()) { 349 logger.process(exchange, throwable); 350 } else { 351 logger.process(exchange, ", Exception: " + throwable.toString()); 352 } 353 } 354 } 355 356 /** 357 * Returns true if the given exchange should be logged in the trace list 358 */ 359 protected boolean shouldLogExchange(Exchange exchange) { 360 return tracer.isEnabled() && (tracer.getTraceFilter() == null || tracer.getTraceFilter().matches(exchange)); 361 } 362 363 /** 364 * Returns true if the given exchange should be logged when an exception was thrown 365 */ 366 protected boolean shouldLogException(Exchange exchange) { 367 return tracer.isTraceExceptions(); 368 } 369 370 /** 371 * Returns whether exchanges coming out of processors should be traced 372 */ 373 public boolean shouldTraceOutExchanges() { 374 return tracer.isTraceOutExchanges(); 375 } 376 377 /** 378 * Returns true if the given node should be logged in the trace list 379 */ 380 protected boolean shouldLogNode(ProcessorDefinition node) { 381 if (node == null) { 382 return false; 383 } 384 if (!tracer.isTraceInterceptors() && (node instanceof InterceptStrategy)) { 385 return false; 386 } 387 return true; 388 } 389 390 private synchronized Producer getTraceEventProducer(Exchange exchange) throws Exception { 391 if (traceEventProducer == null) { 392 // create producer when we have access the the camel context (we dont in doStart) 393 Endpoint endpoint = tracer.getDestination() != null ? tracer.getDestination() : exchange.getContext().getEndpoint(tracer.getDestinationUri()); 394 traceEventProducer = endpoint.createProducer(); 395 ServiceHelper.startService(traceEventProducer); 396 } 397 return traceEventProducer; 398 } 399 400 @Override 401 protected void doStart() throws Exception { 402 super.doStart(); 403 traceEventProducer = null; 404 } 405 406 @Override 407 protected void doStop() throws Exception { 408 super.doStop(); 409 if (traceEventProducer != null) { 410 ServiceHelper.stopService(traceEventProducer); 411 } 412 } 413 414 }