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.List;
020    import java.util.concurrent.CopyOnWriteArrayList;
021    
022    import org.apache.camel.CamelContext;
023    import org.apache.camel.Endpoint;
024    import org.apache.camel.LoggingLevel;
025    import org.apache.camel.Predicate;
026    import org.apache.camel.Processor;
027    import org.apache.camel.Service;
028    import org.apache.camel.model.ProcessorDefinition;
029    import org.apache.camel.model.RouteDefinitionHelper;
030    import org.apache.camel.processor.CamelLogProcessor;
031    import org.apache.camel.spi.ExchangeFormatter;
032    import org.apache.camel.spi.InterceptStrategy;
033    import org.apache.camel.util.CamelLogger;
034    
035    /**
036     * An interceptor strategy for tracing routes
037     *
038     * @version 
039     */
040    public 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.add(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    
346        public boolean isJmxTraceNotifications() {
347            return jmxTraceNotifications;
348        }
349    
350        public void setJmxTraceNotifications(boolean jmxTraceNotifications) {
351            this.jmxTraceNotifications = jmxTraceNotifications;
352        }
353    
354        public int getTraceBodySize() {
355            return traceBodySize;
356        }
357    
358        public void setTraceBodySize(int traceBodySize) {
359            this.traceBodySize = traceBodySize;
360        }
361    
362        public void start() throws Exception {
363            // noop
364        }
365    
366        public void stop() throws Exception {
367            traceHandlers.clear();
368        }
369    
370        @Override
371        public String toString() {
372            return "Tracer";
373        }
374    }