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.interceptor; 018 019import java.util.Collections; 020import java.util.List; 021 022import org.apache.camel.AsyncCallback; 023import org.apache.camel.Exchange; 024import org.apache.camel.Processor; 025import org.apache.camel.impl.AggregateRouteNode; 026import org.apache.camel.impl.DefaultRouteNode; 027import org.apache.camel.impl.DoCatchRouteNode; 028import org.apache.camel.impl.DoFinallyRouteNode; 029import org.apache.camel.impl.OnCompletionRouteNode; 030import org.apache.camel.impl.OnExceptionRouteNode; 031import org.apache.camel.model.AggregateDefinition; 032import org.apache.camel.model.CatchDefinition; 033import org.apache.camel.model.Constants; 034import org.apache.camel.model.FinallyDefinition; 035import org.apache.camel.model.InterceptDefinition; 036import org.apache.camel.model.OnCompletionDefinition; 037import org.apache.camel.model.OnExceptionDefinition; 038import org.apache.camel.model.ProcessorDefinition; 039import org.apache.camel.model.ProcessorDefinitionHelper; 040import org.apache.camel.processor.CamelLogProcessor; 041import org.apache.camel.processor.DefaultMaskingFormatter; 042import org.apache.camel.processor.DelegateAsyncProcessor; 043import org.apache.camel.spi.ExchangeFormatter; 044import org.apache.camel.spi.InterceptStrategy; 045import org.apache.camel.spi.MaskingFormatter; 046import org.apache.camel.spi.RouteContext; 047import org.apache.camel.spi.TracedRouteNodes; 048import org.apache.camel.util.ServiceHelper; 049import org.slf4j.Logger; 050import org.slf4j.LoggerFactory; 051 052/** 053 * An interceptor for debugging and tracing routes 054 * 055 * @version 056 */ 057public class TraceInterceptor extends DelegateAsyncProcessor implements ExchangeFormatter { 058 private static final Logger LOG = LoggerFactory.getLogger(TraceInterceptor.class); 059 060 private CamelLogProcessor logger; 061 062 private final ProcessorDefinition<?> node; 063 private final Tracer tracer; 064 private TraceFormatter formatter; 065 066 private RouteContext routeContext; 067 private List<TraceEventHandler> traceHandlers; 068 069 public TraceInterceptor(ProcessorDefinition<?> node, Processor target, TraceFormatter formatter, Tracer tracer) { 070 super(target); 071 this.tracer = tracer; 072 this.node = node; 073 this.formatter = formatter; 074 this.logger = tracer.getLogger(this); 075 if (tracer.getFormatter() != null) { 076 this.formatter = tracer.getFormatter(); 077 } 078 this.traceHandlers = tracer.getTraceHandlers(); 079 } 080 081 @Override 082 public String toString() { 083 return "TraceInterceptor[" + node + "]"; 084 } 085 086 public void setRouteContext(RouteContext routeContext) { 087 this.routeContext = routeContext; 088 prepareMaskingFormatter(routeContext); 089 } 090 091 private void prepareMaskingFormatter(RouteContext routeContext) { 092 if (routeContext.isLogMask()) { 093 MaskingFormatter formatter = routeContext.getCamelContext().getRegistry().lookupByNameAndType(Constants.CUSTOM_LOG_MASK_REF, MaskingFormatter.class); 094 if (formatter == null) { 095 formatter = new DefaultMaskingFormatter(); 096 } 097 logger.setMaskingFormatter(formatter); 098 } 099 } 100 101 @Override 102 public boolean process(final Exchange exchange, final AsyncCallback callback) { 103 // do not trace if tracing is disabled 104 if (!tracer.isEnabled() || (routeContext != null && !routeContext.isTracing())) { 105 return processor.process(exchange, callback); 106 } 107 108 // interceptor will also trace routes supposed only for TraceEvents so we need to skip 109 // logging TraceEvents to avoid infinite looping 110 if (exchange.getProperty(Exchange.TRACE_EVENT, false, Boolean.class)) { 111 // but we must still process to allow routing of TraceEvents to eg a JPA endpoint 112 return processor.process(exchange, callback); 113 } 114 115 final boolean shouldLog = shouldLogNode(node) && shouldLogExchange(exchange); 116 117 // whether we should trace it or not, some nodes should be skipped as they are abstract 118 // intermediate steps for instance related to on completion 119 boolean trace = true; 120 boolean sync = true; 121 122 // okay this is a regular exchange being routed we might need to log and trace 123 try { 124 // before 125 if (shouldLog) { 126 // traced holds the information about the current traced route path 127 if (exchange.getUnitOfWork() != null) { 128 TracedRouteNodes traced = exchange.getUnitOfWork().getTracedRouteNodes(); 129 130 if (node instanceof OnCompletionDefinition || node instanceof OnExceptionDefinition) { 131 // skip any of these as its just a marker definition 132 trace = false; 133 } else if (ProcessorDefinitionHelper.isFirstChildOfType(OnCompletionDefinition.class, node)) { 134 // special for on completion tracing 135 traceOnCompletion(traced, exchange); 136 } else if (ProcessorDefinitionHelper.isFirstChildOfType(OnExceptionDefinition.class, node)) { 137 // special for on exception 138 traceOnException(traced, exchange); 139 } else if (ProcessorDefinitionHelper.isFirstChildOfType(CatchDefinition.class, node)) { 140 // special for do catch 141 traceDoCatch(traced, exchange); 142 } else if (ProcessorDefinitionHelper.isFirstChildOfType(FinallyDefinition.class, node)) { 143 // special for do finally 144 traceDoFinally(traced, exchange); 145 } else if (ProcessorDefinitionHelper.isFirstChildOfType(AggregateDefinition.class, node)) { 146 // special for aggregate 147 traceAggregate(traced, exchange); 148 } else { 149 // regular so just add it 150 traced.addTraced(new DefaultRouteNode(node, super.getProcessor())); 151 } 152 } else { 153 LOG.trace("Cannot trace as this Exchange does not have an UnitOfWork: {}", exchange); 154 } 155 } 156 157 // log and trace the processor 158 Object state = null; 159 if (shouldLog && trace) { 160 logExchange(exchange); 161 // either call the in or generic trace method depending on OUT has been enabled or not 162 if (tracer.isTraceOutExchanges()) { 163 state = traceExchangeIn(exchange); 164 } else { 165 traceExchange(exchange); 166 } 167 } 168 final Object traceState = state; 169 170 // special for interceptor where we need to keep booking how far we have routed in the intercepted processors 171 if (node.getParent() instanceof InterceptDefinition && exchange.getUnitOfWork() != null) { 172 TracedRouteNodes traced = exchange.getUnitOfWork().getTracedRouteNodes(); 173 traceIntercept((InterceptDefinition) node.getParent(), traced, exchange); 174 } 175 176 // process the exchange 177 sync = processor.process(exchange, new AsyncCallback() { 178 @Override 179 public void done(boolean doneSync) { 180 try { 181 // after (trace out) 182 if (shouldLog && tracer.isTraceOutExchanges()) { 183 logExchange(exchange); 184 traceExchangeOut(exchange, traceState); 185 } 186 } catch (Throwable e) { 187 // some exception occurred in trace logic 188 if (shouldLogException(exchange)) { 189 logException(exchange, e); 190 } 191 exchange.setException(e); 192 } finally { 193 // ensure callback is always invoked 194 callback.done(doneSync); 195 } 196 } 197 }); 198 199 } catch (Throwable e) { 200 // some exception occurred in trace logic 201 if (shouldLogException(exchange)) { 202 logException(exchange, e); 203 } 204 exchange.setException(e); 205 } 206 207 return sync; 208 } 209 210 private void traceOnCompletion(TracedRouteNodes traced, Exchange exchange) { 211 traced.addTraced(new OnCompletionRouteNode()); 212 // do not log and trace as onCompletion should be a new event on its own 213 // add the next step as well so we have onCompletion -> new step 214 traced.addTraced(new DefaultRouteNode(node, super.getProcessor())); 215 } 216 217 private void traceOnException(TracedRouteNodes traced, Exchange exchange) throws Exception { 218 if (traced.getLastNode() != null) { 219 traced.addTraced(new DefaultRouteNode(traced.getLastNode().getProcessorDefinition(), traced.getLastNode().getProcessor())); 220 } 221 traced.addTraced(new OnExceptionRouteNode()); 222 // log and trace so we have the from -> onException event as well 223 logExchange(exchange); 224 traceExchange(exchange); 225 traced.addTraced(new DefaultRouteNode(node, super.getProcessor())); 226 } 227 228 private void traceDoCatch(TracedRouteNodes traced, Exchange exchange) throws Exception { 229 if (traced.getLastNode() != null) { 230 traced.addTraced(new DefaultRouteNode(traced.getLastNode().getProcessorDefinition(), traced.getLastNode().getProcessor())); 231 } 232 traced.addTraced(new DoCatchRouteNode()); 233 // log and trace so we have the from -> doCatch event as well 234 logExchange(exchange); 235 traceExchange(exchange); 236 traced.addTraced(new DefaultRouteNode(node, super.getProcessor())); 237 } 238 239 private void traceDoFinally(TracedRouteNodes traced, Exchange exchange) throws Exception { 240 if (traced.getLastNode() != null) { 241 traced.addTraced(new DefaultRouteNode(traced.getLastNode().getProcessorDefinition(), traced.getLastNode().getProcessor())); 242 } 243 traced.addTraced(new DoFinallyRouteNode()); 244 // log and trace so we have the from -> doFinally event as well 245 logExchange(exchange); 246 traceExchange(exchange); 247 traced.addTraced(new DefaultRouteNode(node, super.getProcessor())); 248 } 249 250 private void traceAggregate(TracedRouteNodes traced, Exchange exchange) { 251 traced.addTraced(new AggregateRouteNode((AggregateDefinition) node.getParent())); 252 traced.addTraced(new DefaultRouteNode(node, super.getProcessor())); 253 } 254 255 protected void traceIntercept(InterceptDefinition intercept, TracedRouteNodes traced, Exchange exchange) throws Exception { 256 // use the counter to get the index of the intercepted processor to be traced 257 Processor last = intercept.getInterceptedProcessor(traced.getAndIncrementCounter(intercept)); 258 // skip doing any double tracing of interceptors, so the last must not be a TraceInterceptor instance 259 if (last != null && !(last instanceof TraceInterceptor)) { 260 traced.addTraced(new DefaultRouteNode(node, last)); 261 262 boolean shouldLog = shouldLogNode(node) && shouldLogExchange(exchange); 263 if (shouldLog) { 264 // log and trace the processor that was intercepted so we can see it 265 logExchange(exchange); 266 traceExchange(exchange); 267 } 268 } 269 } 270 271 public String format(Exchange exchange) { 272 Object msg = formatter.format(this, this.getNode(), exchange); 273 if (msg != null) { 274 return msg.toString(); 275 } else { 276 return null; 277 } 278 } 279 280 // Properties 281 //------------------------------------------------------------------------- 282 public ProcessorDefinition<?> getNode() { 283 return node; 284 } 285 286 public CamelLogProcessor getLogger() { 287 return logger; 288 } 289 290 public TraceFormatter getFormatter() { 291 return formatter; 292 } 293 294 public Tracer getTracer() { 295 return tracer; 296 } 297 298 protected void logExchange(Exchange exchange) throws Exception { 299 // process the exchange that formats and logs it 300 logger.process(exchange); 301 } 302 303 protected void traceExchange(Exchange exchange) throws Exception { 304 for (TraceEventHandler traceHandler : traceHandlers) { 305 traceHandler.traceExchange(node, processor, this, exchange); 306 } 307 } 308 309 protected Object traceExchangeIn(Exchange exchange) throws Exception { 310 Object result = null; 311 for (TraceEventHandler traceHandler : traceHandlers) { 312 Object result1 = traceHandler.traceExchangeIn(node, processor, this, exchange); 313 if (result1 != null) { 314 result = result1; 315 } 316 } 317 return result; 318 } 319 320 protected void traceExchangeOut(Exchange exchange, Object traceState) throws Exception { 321 for (TraceEventHandler traceHandler : traceHandlers) { 322 traceHandler.traceExchangeOut(node, processor, this, exchange, traceState); 323 } 324 } 325 326 protected void logException(Exchange exchange, Throwable throwable) { 327 if (tracer.isTraceExceptions()) { 328 if (tracer.isLogStackTrace()) { 329 logger.process(exchange, throwable); 330 } else { 331 logger.process(exchange, ", Exception: " + throwable.toString()); 332 } 333 } 334 } 335 336 /** 337 * Returns true if the given exchange should be logged in the trace list 338 */ 339 protected boolean shouldLogExchange(Exchange exchange) { 340 return tracer.isEnabled() && (tracer.getTraceFilter() == null || tracer.getTraceFilter().matches(exchange)); 341 } 342 343 /** 344 * Returns true if the given exchange should be logged when an exception was thrown 345 */ 346 protected boolean shouldLogException(Exchange exchange) { 347 return tracer.isTraceExceptions(); 348 } 349 350 /** 351 * Returns whether exchanges coming out of processors should be traced 352 */ 353 public boolean shouldTraceOutExchanges() { 354 return tracer.isTraceOutExchanges(); 355 } 356 357 /** 358 * Returns true if the given node should be logged in the trace list 359 */ 360 protected boolean shouldLogNode(ProcessorDefinition<?> node) { 361 if (node == null) { 362 return false; 363 } 364 if (!tracer.isTraceInterceptors() && (node instanceof InterceptStrategy)) { 365 return false; 366 } 367 return true; 368 } 369 370 @Override 371 protected void doStart() throws Exception { 372 super.doStart(); 373 ServiceHelper.startService(traceHandlers); 374 } 375 376 @Override 377 protected void doStop() throws Exception { 378 super.doStop(); 379 ServiceHelper.stopService(traceHandlers); 380 } 381 382 @Deprecated 383 public void setTraceHandler(TraceEventHandler traceHandler) { 384 traceHandlers = Collections.singletonList(traceHandler); 385 } 386}