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.List;
020import java.util.concurrent.CopyOnWriteArrayList;
021
022import org.apache.camel.CamelContext;
023import org.apache.camel.Endpoint;
024import org.apache.camel.LoggingLevel;
025import org.apache.camel.Predicate;
026import org.apache.camel.Processor;
027import org.apache.camel.Service;
028import org.apache.camel.model.ProcessorDefinition;
029import org.apache.camel.model.RouteDefinitionHelper;
030import org.apache.camel.processor.CamelLogProcessor;
031import org.apache.camel.spi.ExchangeFormatter;
032import org.apache.camel.spi.InterceptStrategy;
033import org.apache.camel.util.CamelLogger;
034
035/**
036 * An interceptor strategy for tracing routes
037 *
038 * @version 
039 */
040public class Tracer implements InterceptStrategy, Service {
041    private static final String JPA_TRACE_EVENT_MESSAGE = "org.apache.camel.processor.interceptor.jpa.JpaTraceEventMessage";
042
043    private TraceFormatter formatter = new DefaultTraceFormatter();
044    private boolean enabled = true;
045    private String logName = Tracer.class.getName();
046    private LoggingLevel logLevel = LoggingLevel.INFO;
047    private Predicate traceFilter;
048    private boolean traceInterceptors;
049    private boolean traceExceptions = true;
050    private boolean logStackTrace;
051    private boolean traceOutExchanges;
052    private String destinationUri;
053    private Endpoint destination;
054    private boolean useJpa;
055    private CamelLogProcessor logger;
056    private TraceInterceptorFactory traceInterceptorFactory = new DefaultTraceInterceptorFactory();
057    private final List<TraceEventHandler> traceHandlers = new CopyOnWriteArrayList<TraceEventHandler>();
058    private String jpaTraceEventMessageClassName = JPA_TRACE_EVENT_MESSAGE;
059    private boolean jmxTraceNotifications;
060    private int traceBodySize = 10000;
061    
062    public Tracer() {
063        traceHandlers.add(new DefaultTraceEventHandler(this));
064    }
065
066    /**
067     * Creates a new tracer.
068     *
069     * @param context Camel context
070     * @return a new tracer
071     */
072    public static Tracer createTracer(CamelContext context) {
073        Tracer tracer = new Tracer();
074        // lets see if we have a formatter if so use it
075        TraceFormatter formatter = context.getRegistry().lookupByNameAndType("traceFormatter", TraceFormatter.class);
076        if (formatter != null) {
077            tracer.setFormatter(formatter);
078        }
079        return tracer;
080    }
081
082    /**
083     * A helper method to return the Tracer instance if one is enabled
084     *
085     * @return the tracer or null if none can be found
086     */
087    public static Tracer getTracer(CamelContext context) {
088        List<InterceptStrategy> list = context.getInterceptStrategies();
089        for (InterceptStrategy interceptStrategy : list) {
090            if (interceptStrategy instanceof Tracer) {
091                return (Tracer) interceptStrategy;
092            }
093        }
094        return null;
095    }
096
097    /**
098     * Gets the logger to be used for tracers that can format and log a given exchange.
099     *
100     * @param formatter the exchange formatter
101     * @return the logger to use
102     */
103    public synchronized CamelLogProcessor getLogger(ExchangeFormatter formatter) {
104        if (logger == null) {
105            logger = new CamelLogProcessor(new CamelLogger(getLogName(), getLogLevel()), formatter);
106        }
107        return logger;
108    }
109
110    public Processor wrapProcessorInInterceptors(CamelContext context, ProcessorDefinition<?> definition,
111                                                 Processor target, Processor nextTarget) throws Exception {
112        // Force the creation of an id, otherwise the id is not available when the trace formatter is
113        // outputting trace information
114        RouteDefinitionHelper.forceAssignIds(context, definition);
115        return getTraceInterceptorFactory().createTraceInterceptor(definition, target, formatter, this);
116    }
117
118    public TraceFormatter getFormatter() {
119        return formatter;
120    }
121
122    public DefaultTraceFormatter getDefaultTraceFormatter() {
123        if (formatter instanceof DefaultTraceFormatter) {
124            return (DefaultTraceFormatter) formatter;
125        }
126        return null;
127    }
128
129    public void setFormatter(TraceFormatter formatter) {
130        this.formatter = formatter;
131    }
132
133    public void setEnabled(boolean flag) {
134        enabled = flag;
135    }
136
137    public boolean isEnabled() {
138        return enabled;
139    }
140
141    public boolean isTraceInterceptors() {
142        return traceInterceptors;
143    }
144
145    /**
146     * Sets whether interceptors should be traced or not
147     */
148    public void setTraceInterceptors(boolean traceInterceptors) {
149        this.traceInterceptors = traceInterceptors;
150    }
151
152    public Predicate getTraceFilter() {
153        return traceFilter;
154    }
155
156    /**
157     * Sets a predicate to be used as filter when tracing
158     */
159    public void setTraceFilter(Predicate traceFilter) {
160        this.traceFilter = traceFilter;
161    }
162
163    public LoggingLevel getLogLevel() {
164        return logLevel;
165    }
166
167    /**
168     * Sets the logging level to output tracing. Will use <tt>INFO</tt> level by default.
169     */
170    public void setLogLevel(LoggingLevel logLevel) {
171        this.logLevel = logLevel;
172        // update logger if its in use
173        if (logger != null) {
174            logger.getLogger().setLevel(logLevel);
175        }
176    }
177
178    public boolean isTraceExceptions() {
179        return traceExceptions;
180    }
181
182    /**
183     * Sets whether thrown exceptions should be traced
184     */
185    public void setTraceExceptions(boolean traceExceptions) {
186        this.traceExceptions = traceExceptions;
187    }
188
189    public boolean isLogStackTrace() {
190        return logStackTrace;
191    }
192
193    /**
194     * Sets whether thrown exception stacktrace should be traced, if disabled then only the exception message is logged
195     */
196    public void setLogStackTrace(boolean logStackTrace) {
197        this.logStackTrace = logStackTrace;
198    }
199
200    public String getLogName() {
201        return logName;
202    }
203
204    /**
205     * Sets the logging name to use.
206     * Will default use <tt>org.apache.camel.processor.interceptor.TraceInterceptor<tt>.
207     */
208    public void setLogName(String logName) {
209        this.logName = logName;
210        // update logger if its in use
211        if (logger != null) {
212            logger.getLogger().setLogName(logName);
213        }
214    }
215
216    /**
217     * Sets whether exchanges coming out of processors should be traced
218     */
219    public void setTraceOutExchanges(boolean traceOutExchanges) {
220        this.traceOutExchanges = traceOutExchanges;
221    }
222
223    public boolean isTraceOutExchanges() {
224        return traceOutExchanges;
225    }
226
227    public String getDestinationUri() {
228        return destinationUri;
229    }
230
231    /**
232     * Sets an optional destination to send the traced Exchange.
233     * <p/>
234     * Can be used to store tracing as files, in a database or whatever. The routing of the Exchange
235     * will happen synchronously and the original route will first continue when this destination routing
236     * has been completed.
237     */
238    public void setDestinationUri(String destinationUri) {
239        this.destinationUri = destinationUri;
240    }
241
242    public Endpoint getDestination() {
243        return destination;
244    }
245
246    /**
247     * See {@link #setDestinationUri(String)}
248     */
249    public void setDestination(Endpoint destination) {
250        this.destination = destination;
251    }
252
253    public boolean isUseJpa() {
254        return useJpa;
255    }
256
257    /**
258     * Sets whether we should use a JpaTraceEventMessage instead of
259     * an ordinary {@link org.apache.camel.processor.interceptor.DefaultTraceEventMessage}
260     * <p/>
261     * Use this to allow persistence of trace events into a database using JPA.
262     * This requires camel-jpa in the classpath.
263     */
264    public void setUseJpa(boolean useJpa) {
265        this.useJpa = useJpa;
266    }
267
268    public TraceInterceptorFactory getTraceInterceptorFactory() {
269        return this.traceInterceptorFactory;
270    }
271
272    /**
273     * Set the factory to be used to create the trace interceptor.
274     * It is expected that the factory will create a subclass of TraceInterceptor.
275     * <p/>
276     * Use this to take complete control of how trace events are handled.
277     * The TraceInterceptorFactory should only be set before any routes are created, hence this
278     * method is not thread safe.
279     */
280    public void setTraceInterceptorFactory(TraceInterceptorFactory traceInterceptorFactory) {
281        this.traceInterceptorFactory = traceInterceptorFactory;
282    }
283
284    /**
285     * 
286     * @return the first trace event handler
287     */
288    @Deprecated
289    public TraceEventHandler getTraceHandler() {
290        return traceHandlers.get(0);
291    }
292    
293    /**
294     * 
295     * @return list of tracehandlers
296     */
297    public List<TraceEventHandler> getTraceHandlers() {
298        return traceHandlers;
299    }
300
301    /**
302     * Set the object to be used to perform tracing.
303     * <p/>
304     * Use this to take more control of how trace events are persisted.
305     * Setting the traceHandler provides a simpler mechanism for controlling tracing
306     * than the TraceInterceptorFactory.
307     * The TraceHandler should only be set before any routes are created, hence this
308     * method is not thread safe.
309     */
310    @Deprecated
311    public void setTraceHandler(TraceEventHandler traceHandler) {
312        this.traceHandlers.clear();
313        this.traceHandlers.add(traceHandler);
314    }
315    
316    /**
317     * Add the given tracehandler
318     */
319    public void addTraceHandler(TraceEventHandler traceHandler) {
320        this.traceHandlers.add(traceHandler);
321    }
322    
323    /**
324     * Remove the given tracehandler
325     */
326    public void removeTraceHandler(TraceEventHandler traceHandler) {
327        this.traceHandlers.remove(traceHandler);
328    }
329
330    public String getJpaTraceEventMessageClassName() {
331        return jpaTraceEventMessageClassName;
332    }
333
334    /**
335     * Set the fully qualified name of the class to be used by the JPA event tracing.
336     * <p/>
337     * The class must exist in the classpath and be available for dynamic loading.
338     * The class name should only be set before any routes are created, hence this
339     * method is not thread safe.
340     */
341    public void setJpaTraceEventMessageClassName(String jpaTraceEventMessageClassName) {
342        this.jpaTraceEventMessageClassName = jpaTraceEventMessageClassName;
343    }
344
345    public boolean isJmxTraceNotifications() {
346        return jmxTraceNotifications;
347    }
348
349    public void setJmxTraceNotifications(boolean jmxTraceNotifications) {
350        this.jmxTraceNotifications = jmxTraceNotifications;
351    }
352
353    public int getTraceBodySize() {
354        return traceBodySize;
355    }
356
357    public void setTraceBodySize(int traceBodySize) {
358        this.traceBodySize = traceBodySize;
359    }
360
361    public void start() throws Exception {
362        // noop
363    }
364
365    public void stop() throws Exception {
366        traceHandlers.clear();
367    }
368
369    @Override
370    public String toString() {
371        return "Tracer";
372    }
373}