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    }