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}