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.Processor;
026 import org.apache.camel.Producer;
027 import org.apache.camel.impl.AggregateRouteNode;
028 import org.apache.camel.impl.DefaultExchange;
029 import org.apache.camel.impl.DefaultRouteNode;
030 import org.apache.camel.impl.DoCatchRouteNode;
031 import org.apache.camel.impl.DoFinallyRouteNode;
032 import org.apache.camel.impl.OnCompletionRouteNode;
033 import org.apache.camel.impl.OnExceptionRouteNode;
034 import org.apache.camel.model.AggregateDefinition;
035 import org.apache.camel.model.CatchDefinition;
036 import org.apache.camel.model.FinallyDefinition;
037 import org.apache.camel.model.InterceptDefinition;
038 import org.apache.camel.model.OnCompletionDefinition;
039 import org.apache.camel.model.OnExceptionDefinition;
040 import org.apache.camel.model.ProcessorDefinition;
041 import org.apache.camel.model.ProcessorDefinitionHelper;
042 import org.apache.camel.processor.DelegateProcessor;
043 import org.apache.camel.processor.Logger;
044 import org.apache.camel.spi.ExchangeFormatter;
045 import org.apache.camel.spi.InterceptStrategy;
046 import org.apache.camel.spi.RouteContext;
047 import org.apache.camel.spi.TracedRouteNodes;
048 import org.apache.camel.util.IntrospectionSupport;
049 import org.apache.camel.util.ObjectHelper;
050 import org.apache.camel.util.ServiceHelper;
051 import org.apache.commons.logging.Log;
052 import org.apache.commons.logging.LogFactory;
053
054 /**
055 * An interceptor for debugging and tracing routes
056 *
057 * @version $Revision: 893110 $
058 */
059 public class TraceInterceptor extends DelegateProcessor implements ExchangeFormatter {
060 private static final transient Log LOG = LogFactory.getLog(TraceInterceptor.class);
061 private static final String JPA_TRACE_EVENT_MESSAGE = "org.apache.camel.processor.interceptor.jpa.JpaTraceEventMessage";
062 private Logger logger;
063 private Producer traceEventProducer;
064 private final ProcessorDefinition node;
065 private final Tracer tracer;
066 private TraceFormatter formatter;
067 private Class<?> jpaTraceEventMessageClass;
068 private RouteContext routeContext;
069
070 public TraceInterceptor(ProcessorDefinition node, Processor target, TraceFormatter formatter, Tracer tracer) {
071 super(target);
072 this.tracer = tracer;
073 this.node = node;
074 this.formatter = formatter;
075 this.logger = tracer.getLogger(this);
076 if (tracer.getFormatter() != null) {
077 this.formatter = tracer.getFormatter();
078 }
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 }
089
090 public void process(final Exchange exchange) throws Exception {
091 // do not trace if tracing is disabled
092 if (!tracer.isEnabled() || (routeContext != null && !routeContext.isTracing())) {
093 super.proceed(exchange);
094 return;
095 }
096
097 // interceptor will also trace routes supposed only for TraceEvents so we need to skip
098 // logging TraceEvents to avoid infinite looping
099 if (exchange.getProperty(Exchange.TRACE_EVENT, false, Boolean.class)) {
100 // but we must still process to allow routing of TraceEvents to eg a JPA endpoint
101 super.process(exchange);
102 return;
103 }
104
105 boolean shouldLog = shouldLogNode(node) && shouldLogExchange(exchange);
106
107 // whether we should trace it or not, some nodes should be skipped as they are abstract
108 // intermediate steps for instance related to on completion
109 boolean trace = true;
110
111 // okay this is a regular exchange being routed we might need to log and trace
112 try {
113 // before
114 if (shouldLog) {
115 // traced holds the information about the current traced route path
116 if (exchange.getUnitOfWork() != null) {
117 TracedRouteNodes traced = exchange.getUnitOfWork().getTracedRouteNodes();
118
119 if (node instanceof OnCompletionDefinition || node instanceof OnExceptionDefinition) {
120 // skip any of these as its just a marker definition
121 trace = false;
122 } else if (ProcessorDefinitionHelper.isFirstChildOfType(OnCompletionDefinition.class, node)) {
123 // special for on completion tracing
124 traceOnCompletion(traced, exchange);
125 } else if (ProcessorDefinitionHelper.isFirstChildOfType(OnExceptionDefinition.class, node)) {
126 // special for on exception
127 traceOnException(traced, exchange);
128 } else if (ProcessorDefinitionHelper.isFirstChildOfType(CatchDefinition.class, node)) {
129 // special for do catch
130 traceDoCatch(traced, exchange);
131 } else if (ProcessorDefinitionHelper.isFirstChildOfType(FinallyDefinition.class, node)) {
132 // special for do finally
133 traceDoFinally(traced, exchange);
134 } else if (ProcessorDefinitionHelper.isFirstChildOfType(AggregateDefinition.class, node)) {
135 // special for aggregate
136 traceAggregate(traced, exchange);
137 } else {
138 // regular so just add it
139 traced.addTraced(new DefaultRouteNode(node, super.getProcessor()));
140 }
141 } else {
142 if (LOG.isTraceEnabled()) {
143 LOG.trace("Cannot trace as this Exchange does not have an UnitOfWork: " + exchange);
144 }
145 }
146 }
147
148 // log and trace the processor
149 if (shouldLog && trace) {
150 logExchange(exchange);
151 traceExchange(exchange);
152 }
153
154 // special for interceptor where we need to keep booking how far we have routed in the intercepted processors
155 if (node.getParent() instanceof InterceptDefinition && exchange.getUnitOfWork() != null) {
156 TracedRouteNodes traced = exchange.getUnitOfWork().getTracedRouteNodes();
157 traceIntercept((InterceptDefinition) node.getParent(), traced, exchange);
158 }
159
160 // process the exchange
161 super.proceed(exchange);
162
163 // after (trace out)
164 if (shouldLog && tracer.isTraceOutExchanges()) {
165 logExchange(exchange);
166 traceExchange(exchange);
167 }
168 } catch (Exception e) {
169 if (shouldLogException(exchange)) {
170 logException(exchange, e);
171 }
172 throw e;
173 }
174 }
175
176 private void traceOnCompletion(TracedRouteNodes traced, Exchange exchange) {
177 traced.addTraced(new OnCompletionRouteNode());
178 // do not log and trace as onCompletion should be a new event on its own
179 // add the next step as well so we have onCompletion -> new step
180 traced.addTraced(new DefaultRouteNode(node, super.getProcessor()));
181 }
182
183 private void traceOnException(TracedRouteNodes traced, Exchange exchange) throws Exception {
184 traced.addTraced(new DefaultRouteNode(traced.getLastNode().getProcessorDefinition(), traced.getLastNode().getProcessor()));
185 traced.addTraced(new OnExceptionRouteNode());
186 // log and trace so we have the from -> onException event as well
187 logExchange(exchange);
188 traceExchange(exchange);
189 traced.addTraced(new DefaultRouteNode(node, super.getProcessor()));
190 }
191
192 private void traceDoCatch(TracedRouteNodes traced, Exchange exchange) throws Exception {
193 traced.addTraced(new DefaultRouteNode(traced.getLastNode().getProcessorDefinition(), traced.getLastNode().getProcessor()));
194 traced.addTraced(new DoCatchRouteNode());
195 // log and trace so we have the from -> doCatch event as well
196 logExchange(exchange);
197 traceExchange(exchange);
198 traced.addTraced(new DefaultRouteNode(node, super.getProcessor()));
199 }
200
201 private void traceDoFinally(TracedRouteNodes traced, Exchange exchange) throws Exception {
202 traced.addTraced(new DefaultRouteNode(traced.getLastNode().getProcessorDefinition(), traced.getLastNode().getProcessor()));
203 traced.addTraced(new DoFinallyRouteNode());
204 // log and trace so we have the from -> doFinally event as well
205 logExchange(exchange);
206 traceExchange(exchange);
207 traced.addTraced(new DefaultRouteNode(node, super.getProcessor()));
208 }
209
210 private void traceAggregate(TracedRouteNodes traced, Exchange exchange) {
211 traced.addTraced(new AggregateRouteNode((AggregateDefinition) node.getParent()));
212 traced.addTraced(new DefaultRouteNode(node, super.getProcessor()));
213 }
214
215 protected void traceIntercept(InterceptDefinition intercept, TracedRouteNodes traced, Exchange exchange) throws Exception {
216 // use the counter to get the index of the intercepted processor to be traced
217 Processor last = intercept.getInterceptedProcessor(traced.getAndIncrementCounter(intercept));
218 if (last != null) {
219 traced.addTraced(new DefaultRouteNode(node, last));
220
221 boolean shouldLog = shouldLogNode(node) && shouldLogExchange(exchange);
222 if (shouldLog) {
223 // log and trace the processor that was intercepted so we can see it
224 logExchange(exchange);
225 traceExchange(exchange);
226 }
227 }
228 }
229
230 public Object format(Exchange exchange) {
231 return formatter.format(this, this.getNode(), exchange);
232 }
233
234 // Properties
235 //-------------------------------------------------------------------------
236 public ProcessorDefinition<?> getNode() {
237 return node;
238 }
239
240 public Logger getLogger() {
241 return logger;
242 }
243
244 public TraceFormatter getFormatter() {
245 return formatter;
246 }
247
248 public Tracer getTracer() {
249 return tracer;
250 }
251
252 protected void logExchange(Exchange exchange) {
253 // process the exchange that formats and logs it
254 logger.process(exchange);
255 }
256
257 @SuppressWarnings("unchecked")
258 protected void traceExchange(Exchange exchange) throws Exception {
259 // should we send a trace event to an optional destination?
260 if (tracer.getDestination() != null || tracer.getDestinationUri() != null) {
261
262 // create event exchange and add event information
263 Date timestamp = new Date();
264 Exchange event = new DefaultExchange(exchange);
265 event.setProperty(Exchange.TRACE_EVENT_NODE_ID, node.getId());
266 event.setProperty(Exchange.TRACE_EVENT_TIMESTAMP, timestamp);
267 // keep a reference to the original exchange in case its needed
268 event.setProperty(Exchange.TRACE_EVENT_EXCHANGE, exchange);
269
270 // create event message to sent as in body containing event information such as
271 // from node, to node, etc.
272 TraceEventMessage msg = new DefaultTraceEventMessage(timestamp, node, exchange);
273
274 // should we use ordinary or jpa objects
275 if (tracer.isUseJpa()) {
276 LOG.trace("Using class: " + JPA_TRACE_EVENT_MESSAGE + " for tracing event messages");
277
278 // load the jpa event message class
279 loadJpaTraceEventMessageClass(exchange);
280 // create a new instance of the event message class
281 Object jpa = ObjectHelper.newInstance(jpaTraceEventMessageClass);
282
283 // copy options from event to jpa
284 Map options = new HashMap();
285 IntrospectionSupport.getProperties(msg, options, null);
286 IntrospectionSupport.setProperties(jpa, options);
287 // and set the timestamp as its not a String type
288 IntrospectionSupport.setProperty(jpa, "timestamp", msg.getTimestamp());
289
290 event.getIn().setBody(jpa);
291 } else {
292 event.getIn().setBody(msg);
293 }
294
295 // marker property to indicate its a tracing event being routed in case
296 // new Exchange instances is created during trace routing so we can check
297 // for this marker when interceptor also kick in during routing of trace events
298 event.setProperty(Exchange.TRACE_EVENT, Boolean.TRUE);
299 try {
300 // process the trace route
301 getTraceEventProducer(exchange).process(event);
302 } catch (Exception e) {
303 // log and ignore this as the original Exchange should be allowed to continue
304 LOG.error("Error processing trace event (original Exchange will continue): " + event, e);
305 }
306 }
307 }
308
309 private synchronized void loadJpaTraceEventMessageClass(Exchange exchange) {
310 if (jpaTraceEventMessageClass == null) {
311 jpaTraceEventMessageClass = exchange.getContext().getClassResolver().resolveClass(JPA_TRACE_EVENT_MESSAGE);
312 if (jpaTraceEventMessageClass == null) {
313 throw new IllegalArgumentException("Cannot find class: " + JPA_TRACE_EVENT_MESSAGE
314 + ". Make sure camel-jpa.jar is in the classpath.");
315 }
316 }
317 }
318
319 protected void logException(Exchange exchange, Throwable throwable) {
320 if (tracer.isTraceExceptions()) {
321 if (tracer.isLogStackTrace()) {
322 logger.process(exchange, throwable);
323 } else {
324 logger.process(exchange, ", Exception: " + throwable.toString());
325 }
326 }
327 }
328
329 /**
330 * Returns true if the given exchange should be logged in the trace list
331 */
332 protected boolean shouldLogExchange(Exchange exchange) {
333 return tracer.isEnabled() && (tracer.getTraceFilter() == null || tracer.getTraceFilter().matches(exchange));
334 }
335
336 /**
337 * Returns true if the given exchange should be logged when an exception was thrown
338 */
339 protected boolean shouldLogException(Exchange exchange) {
340 return tracer.isTraceExceptions();
341 }
342
343 /**
344 * Returns whether exchanges coming out of processors should be traced
345 */
346 public boolean shouldTraceOutExchanges() {
347 return tracer.isTraceOutExchanges();
348 }
349
350 /**
351 * Returns true if the given node should be logged in the trace list
352 */
353 protected boolean shouldLogNode(ProcessorDefinition<?> node) {
354 if (node == null) {
355 return false;
356 }
357 if (!tracer.isTraceInterceptors() && (node instanceof InterceptStrategy)) {
358 return false;
359 }
360 return true;
361 }
362
363 private synchronized Producer getTraceEventProducer(Exchange exchange) throws Exception {
364 if (traceEventProducer == null) {
365 // create producer when we have access the the camel context (we dont in doStart)
366 Endpoint endpoint = tracer.getDestination() != null ? tracer.getDestination() : exchange.getContext().getEndpoint(tracer.getDestinationUri());
367 traceEventProducer = endpoint.createProducer();
368 ServiceHelper.startService(traceEventProducer);
369 }
370 return traceEventProducer;
371 }
372
373 @Override
374 protected void doStart() throws Exception {
375 super.doStart();
376 traceEventProducer = null;
377 }
378
379 @Override
380 protected void doStop() throws Exception {
381 super.doStop();
382 if (traceEventProducer != null) {
383 ServiceHelper.stopService(traceEventProducer);
384 }
385 }
386
387 }