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.ArrayList;
020import java.util.List;
021import java.util.Queue;
022import java.util.concurrent.ArrayBlockingQueue;
023import java.util.concurrent.atomic.AtomicLong;
024
025import org.apache.camel.CamelContext;
026import org.apache.camel.Exchange;
027import org.apache.camel.Predicate;
028import org.apache.camel.Processor;
029import org.apache.camel.api.management.mbean.BacklogTracerEventMessage;
030import org.apache.camel.model.ProcessorDefinition;
031import org.apache.camel.model.ProcessorDefinitionHelper;
032import org.apache.camel.model.RouteDefinition;
033import org.apache.camel.spi.InterceptStrategy;
034import org.apache.camel.support.ServiceSupport;
035import org.apache.camel.util.EndpointHelper;
036import org.apache.camel.util.ObjectHelper;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040/**
041 * A tracer used for message tracing, storing a copy of the message details in a backlog.
042 * <p/>
043 * This tracer allows to store message tracers per node in the Camel routes. The tracers
044 * is stored in a backlog queue (FIFO based) which allows to pull the traced messages on demand.
045 */
046public class BacklogTracer extends ServiceSupport implements InterceptStrategy {
047
048    // lets limit the tracer to 100 thousand messages in total
049    public static final int MAX_BACKLOG_SIZE = 100 * 1000;
050    private static final Logger LOG = LoggerFactory.getLogger(BacklogTracer.class);
051    private final CamelContext camelContext;
052    private boolean enabled;
053    private final AtomicLong traceCounter = new AtomicLong(0);
054    // use a queue with a upper limit to avoid storing too many messages
055    private final Queue<DefaultBacklogTracerEventMessage> queue = new ArrayBlockingQueue<DefaultBacklogTracerEventMessage>(MAX_BACKLOG_SIZE);
056    // how many of the last messages to keep in the backlog at total
057    private int backlogSize = 1000;
058    private boolean removeOnDump = true;
059    private int bodyMaxChars = 128 * 1024;
060    private boolean bodyIncludeStreams;
061    private boolean bodyIncludeFiles = true;
062    // a pattern to filter tracing nodes
063    private String tracePattern;
064    private String[] patterns;
065    private String traceFilter;
066    private Predicate predicate;
067
068    public BacklogTracer(CamelContext camelContext) {
069        this.camelContext = camelContext;
070    }
071
072    public Queue<DefaultBacklogTracerEventMessage> getQueue() {
073        return queue;
074    }
075
076    @Override
077    @Deprecated
078    public Processor wrapProcessorInInterceptors(CamelContext context, ProcessorDefinition<?> definition, Processor target, Processor nextTarget) throws Exception {
079        throw new UnsupportedOperationException("Deprecated");
080    }
081
082    /**
083     * Creates a new backlog tracer.
084     *
085     * @param context Camel context
086     * @return a new backlog tracer
087     */
088    public static BacklogTracer createTracer(CamelContext context) {
089        BacklogTracer tracer = new BacklogTracer(context);
090        return tracer;
091    }
092
093    /**
094     * A helper method to return the BacklogTracer instance if one is enabled
095     *
096     * @return the backlog tracer or null if none can be found
097     */
098    public static BacklogTracer getBacklogTracer(CamelContext context) {
099        List<InterceptStrategy> list = context.getInterceptStrategies();
100        for (InterceptStrategy interceptStrategy : list) {
101            if (interceptStrategy instanceof BacklogTracer) {
102                return (BacklogTracer) interceptStrategy;
103            }
104        }
105        return null;
106    }
107
108    /**
109     * Whether or not to trace the given processor definition.
110     *
111     * @param definition the processor definition
112     * @param exchange   the exchange
113     * @return <tt>true</tt> to trace, <tt>false</tt> to skip tracing
114     */
115    public boolean shouldTrace(ProcessorDefinition<?> definition, Exchange exchange) {
116        if (!enabled) {
117            return false;
118        }
119
120        boolean pattern = true;
121        boolean filter = true;
122
123        if (patterns != null) {
124            pattern = shouldTracePattern(definition);
125        }
126        if (predicate != null) {
127            filter = shouldTraceFilter(exchange);
128        }
129
130        if (LOG.isTraceEnabled()) {
131            LOG.trace("Should trace evaluated {} -> pattern: {}, filter: {}", new Object[]{definition.getId(), pattern, filter});
132        }
133        return pattern && filter;
134    }
135
136    private boolean shouldTracePattern(ProcessorDefinition<?> definition) {
137        for (String pattern : patterns) {
138            // match either route id, or node id
139            String id = definition.getId();
140            // use matchPattern method from endpoint helper that has a good matcher we use in Camel
141            if (EndpointHelper.matchPattern(id, pattern)) {
142                return true;
143            }
144            RouteDefinition route = ProcessorDefinitionHelper.getRoute(definition);
145            if (route != null) {
146                id = route.getId();
147                if (EndpointHelper.matchPattern(id, pattern)) {
148                    return true;
149                }
150            }
151        }
152        // not matched the pattern
153        return false;
154    }
155
156    private boolean shouldTraceFilter(Exchange exchange) {
157        return predicate.matches(exchange);
158    }
159
160    public boolean isEnabled() {
161        return enabled;
162    }
163
164    public void setEnabled(boolean enabled) {
165        this.enabled = enabled;
166    }
167
168    public int getBacklogSize() {
169        return backlogSize;
170    }
171
172    public void setBacklogSize(int backlogSize) {
173        if (backlogSize <= 0) {
174            throw new IllegalArgumentException("The backlog size must be a positive number, was: " + backlogSize);
175        }
176        if (backlogSize > MAX_BACKLOG_SIZE) {
177            throw new IllegalArgumentException("The backlog size cannot be greater than the max size of " + MAX_BACKLOG_SIZE + ", was: " + backlogSize);
178        }
179        this.backlogSize = backlogSize;
180    }
181
182    public boolean isRemoveOnDump() {
183        return removeOnDump;
184    }
185
186    public void setRemoveOnDump(boolean removeOnDump) {
187        this.removeOnDump = removeOnDump;
188    }
189
190    public int getBodyMaxChars() {
191        return bodyMaxChars;
192    }
193
194    public void setBodyMaxChars(int bodyMaxChars) {
195        this.bodyMaxChars = bodyMaxChars;
196    }
197
198    public boolean isBodyIncludeStreams() {
199        return bodyIncludeStreams;
200    }
201
202    public void setBodyIncludeStreams(boolean bodyIncludeStreams) {
203        this.bodyIncludeStreams = bodyIncludeStreams;
204    }
205
206    public boolean isBodyIncludeFiles() {
207        return bodyIncludeFiles;
208    }
209
210    public void setBodyIncludeFiles(boolean bodyIncludeFiles) {
211        this.bodyIncludeFiles = bodyIncludeFiles;
212    }
213
214    public String getTracePattern() {
215        return tracePattern;
216    }
217
218    public void setTracePattern(String tracePattern) {
219        this.tracePattern = tracePattern;
220        if (tracePattern != null) {
221            // the pattern can have multiple nodes separated by comma
222            this.patterns = tracePattern.split(",");
223        } else {
224            this.patterns = null;
225        }
226    }
227
228    public String getTraceFilter() {
229        return traceFilter;
230    }
231
232    public void setTraceFilter(String filter) {
233        this.traceFilter = filter;
234        if (filter != null) {
235            // assume simple language
236            String name = ObjectHelper.before(filter, ":");
237            if (name == null) {
238                // use simple language by default
239                name = "simple";
240            }
241            predicate = camelContext.resolveLanguage(name).createPredicate(filter);
242        }
243    }
244
245    public long getTraceCounter() {
246        return traceCounter.get();
247    }
248
249    public void resetTraceCounter() {
250        traceCounter.set(0);
251    }
252
253    public List<BacklogTracerEventMessage> dumpTracedMessages(String nodeId) {
254        List<BacklogTracerEventMessage> answer = new ArrayList<BacklogTracerEventMessage>();
255        if (nodeId != null) {
256            for (DefaultBacklogTracerEventMessage message : queue) {
257                if (nodeId.equals(message.getToNode()) || nodeId.equals(message.getRouteId())) {
258                    answer.add(message);
259                }
260            }
261        }
262
263        if (removeOnDump) {
264            queue.removeAll(answer);
265        }
266
267        return answer;
268    }
269
270    public String dumpTracedMessagesAsXml(String nodeId) {
271        List<BacklogTracerEventMessage> events = dumpTracedMessages(nodeId);
272
273        StringBuilder sb = new StringBuilder();
274        sb.append("<").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
275        for (BacklogTracerEventMessage event : events) {
276            sb.append("\n").append(event.toXml(2));
277        }
278        sb.append("\n</").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
279        return sb.toString();
280    }
281
282    public List<BacklogTracerEventMessage> dumpAllTracedMessages() {
283        List<BacklogTracerEventMessage> answer = new ArrayList<BacklogTracerEventMessage>();
284        answer.addAll(queue);
285        if (isRemoveOnDump()) {
286            queue.clear();
287        }
288        return answer;
289    }
290
291    public String dumpAllTracedMessagesAsXml() {
292        List<BacklogTracerEventMessage> events = dumpAllTracedMessages();
293
294        StringBuilder sb = new StringBuilder();
295        sb.append("<").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
296        for (BacklogTracerEventMessage event : events) {
297            sb.append("\n").append(event.toXml(2));
298        }
299        sb.append("\n</").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
300        return sb.toString();
301    }
302
303    public void clear() {
304        queue.clear();
305    }
306
307    public long incrementTraceCounter() {
308        return traceCounter.incrementAndGet();
309    }
310
311    @Override
312    protected void doStart() throws Exception {
313    }
314
315    @Override
316    protected void doStop() throws Exception {
317        queue.clear();
318    }
319
320}