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.LinkedBlockingQueue;
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 final class BacklogTracer extends ServiceSupport implements InterceptStrategy {
047
048    // lets limit the tracer to 10 thousand messages in total
049    public static final int MAX_BACKLOG_SIZE = 10 * 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<BacklogTracerEventMessage> queue = new LinkedBlockingQueue<>(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    private BacklogTracer(CamelContext camelContext) {
069        this.camelContext = camelContext;
070    }
071
072    @Override
073    @Deprecated
074    public Processor wrapProcessorInInterceptors(CamelContext context, ProcessorDefinition<?> definition, Processor target, Processor nextTarget) throws Exception {
075        throw new UnsupportedOperationException("Deprecated");
076    }
077
078    /**
079     * Creates a new backlog tracer.
080     *
081     * @param context Camel context
082     * @return a new backlog tracer
083     */
084    public static BacklogTracer createTracer(CamelContext context) {
085        return new BacklogTracer(context);
086    }
087
088    /**
089     * A helper method to return the BacklogTracer instance if one is enabled
090     *
091     * @return the backlog tracer or null if none can be found
092     */
093    public static BacklogTracer getBacklogTracer(CamelContext context) {
094        List<InterceptStrategy> list = context.getInterceptStrategies();
095        for (InterceptStrategy interceptStrategy : list) {
096            if (interceptStrategy instanceof BacklogTracer) {
097                return (BacklogTracer) interceptStrategy;
098            }
099        }
100        return null;
101    }
102
103    /**
104     * Whether or not to trace the given processor definition.
105     *
106     * @param definition the processor definition
107     * @param exchange   the exchange
108     * @return <tt>true</tt> to trace, <tt>false</tt> to skip tracing
109     */
110    public boolean shouldTrace(ProcessorDefinition<?> definition, Exchange exchange) {
111        if (!enabled) {
112            return false;
113        }
114
115        boolean pattern = true;
116        boolean filter = true;
117
118        if (patterns != null) {
119            pattern = shouldTracePattern(definition);
120        }
121        if (predicate != null) {
122            filter = shouldTraceFilter(exchange);
123        }
124
125        if (LOG.isTraceEnabled()) {
126            LOG.trace("Should trace evaluated {} -> pattern: {}, filter: {}", new Object[]{definition.getId(), pattern, filter});
127        }
128        return pattern && filter;
129    }
130
131    private boolean shouldTracePattern(ProcessorDefinition<?> definition) {
132        for (String pattern : patterns) {
133            // match either route id, or node id
134            String id = definition.getId();
135            // use matchPattern method from endpoint helper that has a good matcher we use in Camel
136            if (EndpointHelper.matchPattern(id, pattern)) {
137                return true;
138            }
139            RouteDefinition route = ProcessorDefinitionHelper.getRoute(definition);
140            if (route != null) {
141                id = route.getId();
142                if (EndpointHelper.matchPattern(id, pattern)) {
143                    return true;
144                }
145            }
146        }
147        // not matched the pattern
148        return false;
149    }
150
151    public void traceEvent(DefaultBacklogTracerEventMessage event) {
152        if (!enabled) {
153            return;
154        }
155
156        // ensure there is space on the queue by polling until at least single slot is free
157        int drain = queue.size() - backlogSize + 1;
158        if (drain > 0) {
159            for (int i = 0; i < drain; i++) {
160                queue.poll();
161            }
162        }
163
164        queue.add(event);
165    }
166
167    private boolean shouldTraceFilter(Exchange exchange) {
168        return predicate.matches(exchange);
169    }
170
171    public boolean isEnabled() {
172        return enabled;
173    }
174
175    public void setEnabled(boolean enabled) {
176        this.enabled = enabled;
177    }
178
179    public int getBacklogSize() {
180        return backlogSize;
181    }
182
183    public void setBacklogSize(int backlogSize) {
184        if (backlogSize <= 0) {
185            throw new IllegalArgumentException("The backlog size must be a positive number, was: " + backlogSize);
186        }
187        if (backlogSize > MAX_BACKLOG_SIZE) {
188            throw new IllegalArgumentException("The backlog size cannot be greater than the max size of " + MAX_BACKLOG_SIZE + ", was: " + backlogSize);
189        }
190        this.backlogSize = backlogSize;
191    }
192
193    public boolean isRemoveOnDump() {
194        return removeOnDump;
195    }
196
197    public void setRemoveOnDump(boolean removeOnDump) {
198        this.removeOnDump = removeOnDump;
199    }
200
201    public int getBodyMaxChars() {
202        return bodyMaxChars;
203    }
204
205    public void setBodyMaxChars(int bodyMaxChars) {
206        this.bodyMaxChars = bodyMaxChars;
207    }
208
209    public boolean isBodyIncludeStreams() {
210        return bodyIncludeStreams;
211    }
212
213    public void setBodyIncludeStreams(boolean bodyIncludeStreams) {
214        this.bodyIncludeStreams = bodyIncludeStreams;
215    }
216
217    public boolean isBodyIncludeFiles() {
218        return bodyIncludeFiles;
219    }
220
221    public void setBodyIncludeFiles(boolean bodyIncludeFiles) {
222        this.bodyIncludeFiles = bodyIncludeFiles;
223    }
224
225    public String getTracePattern() {
226        return tracePattern;
227    }
228
229    public void setTracePattern(String tracePattern) {
230        this.tracePattern = tracePattern;
231        if (tracePattern != null) {
232            // the pattern can have multiple nodes separated by comma
233            this.patterns = tracePattern.split(",");
234        } else {
235            this.patterns = null;
236        }
237    }
238
239    public String getTraceFilter() {
240        return traceFilter;
241    }
242
243    public void setTraceFilter(String filter) {
244        this.traceFilter = filter;
245        if (filter != null) {
246            // assume simple language
247            String name = ObjectHelper.before(filter, ":");
248            if (name == null) {
249                // use simple language by default
250                name = "simple";
251            }
252            predicate = camelContext.resolveLanguage(name).createPredicate(filter);
253        }
254    }
255
256    public long getTraceCounter() {
257        return traceCounter.get();
258    }
259
260    public void resetTraceCounter() {
261        traceCounter.set(0);
262    }
263
264    public List<BacklogTracerEventMessage> dumpTracedMessages(String nodeId) {
265        List<BacklogTracerEventMessage> answer = new ArrayList<>();
266        if (nodeId != null) {
267            for (BacklogTracerEventMessage message : queue) {
268                if (nodeId.equals(message.getToNode()) || nodeId.equals(message.getRouteId())) {
269                    answer.add(message);
270                }
271            }
272        }
273
274        if (removeOnDump) {
275            queue.removeAll(answer);
276        }
277
278        return answer;
279    }
280
281    public String dumpTracedMessagesAsXml(String nodeId) {
282        List<BacklogTracerEventMessage> events = dumpTracedMessages(nodeId);
283
284        StringBuilder sb = new StringBuilder();
285        sb.append("<").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
286        for (BacklogTracerEventMessage event : events) {
287            sb.append("\n").append(event.toXml(2));
288        }
289        sb.append("\n</").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
290        return sb.toString();
291    }
292
293    public List<BacklogTracerEventMessage> dumpAllTracedMessages() {
294        List<BacklogTracerEventMessage> answer = new ArrayList<>();
295        answer.addAll(queue);
296        if (isRemoveOnDump()) {
297            queue.clear();
298        }
299        return answer;
300    }
301
302    public String dumpAllTracedMessagesAsXml() {
303        List<BacklogTracerEventMessage> events = dumpAllTracedMessages();
304
305        StringBuilder sb = new StringBuilder();
306        sb.append("<").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
307        for (BacklogTracerEventMessage event : events) {
308            sb.append("\n").append(event.toXml(2));
309        }
310        sb.append("\n</").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
311        return sb.toString();
312    }
313
314    public void clear() {
315        queue.clear();
316    }
317
318    public long incrementTraceCounter() {
319        return traceCounter.incrementAndGet();
320    }
321
322    @Override
323    protected void doStart() throws Exception {
324    }
325
326    @Override
327    protected void doStop() throws Exception {
328        queue.clear();
329    }
330
331}