org.apache.camel.processor.interceptor.BacklogTracer Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.processor.interceptor;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Predicate;
import org.apache.camel.Processor;
import org.apache.camel.api.management.mbean.BacklogTracerEventMessage;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.model.ProcessorDefinitionHelper;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.spi.InterceptStrategy;
import org.apache.camel.support.ServiceSupport;
import org.apache.camel.util.EndpointHelper;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A tracer used for message tracing, storing a copy of the message details in a backlog.
*
* This tracer allows to store message tracers per node in the Camel routes. The tracers
* is stored in a backlog queue (FIFO based) which allows to pull the traced messages on demand.
*/
public class BacklogTracer extends ServiceSupport implements InterceptStrategy {
// lets limit the tracer to 100 thousand messages in total
public static final int MAX_BACKLOG_SIZE = 100 * 1000;
private static final Logger LOG = LoggerFactory.getLogger(BacklogTracer.class);
private final CamelContext camelContext;
private boolean enabled;
private final AtomicLong traceCounter = new AtomicLong(0);
// use a queue with a upper limit to avoid storing too many messages
private final Queue queue = new ArrayBlockingQueue(MAX_BACKLOG_SIZE);
// how many of the last messages to keep in the backlog at total
private int backlogSize = 1000;
private boolean removeOnDump = true;
private int bodyMaxChars = 128 * 1024;
private boolean bodyIncludeStreams;
private boolean bodyIncludeFiles = true;
// a pattern to filter tracing nodes
private String tracePattern;
private String[] patterns;
private String traceFilter;
private Predicate predicate;
public BacklogTracer(CamelContext camelContext) {
this.camelContext = camelContext;
}
public Queue getQueue() {
return queue;
}
@Override
@Deprecated
public Processor wrapProcessorInInterceptors(CamelContext context, ProcessorDefinition definition, Processor target, Processor nextTarget) throws Exception {
throw new UnsupportedOperationException("Deprecated");
}
/**
* Creates a new backlog tracer.
*
* @param context Camel context
* @return a new backlog tracer
*/
public static BacklogTracer createTracer(CamelContext context) {
BacklogTracer tracer = new BacklogTracer(context);
return tracer;
}
/**
* A helper method to return the BacklogTracer instance if one is enabled
*
* @return the backlog tracer or null if none can be found
*/
public static BacklogTracer getBacklogTracer(CamelContext context) {
List list = context.getInterceptStrategies();
for (InterceptStrategy interceptStrategy : list) {
if (interceptStrategy instanceof BacklogTracer) {
return (BacklogTracer) interceptStrategy;
}
}
return null;
}
/**
* Whether or not to trace the given processor definition.
*
* @param definition the processor definition
* @param exchange the exchange
* @return true to trace, false to skip tracing
*/
public boolean shouldTrace(ProcessorDefinition definition, Exchange exchange) {
if (!enabled) {
return false;
}
boolean pattern = true;
boolean filter = true;
if (patterns != null) {
pattern = shouldTracePattern(definition);
}
if (predicate != null) {
filter = shouldTraceFilter(exchange);
}
if (LOG.isTraceEnabled()) {
LOG.trace("Should trace evaluated {} -> pattern: {}, filter: {}", new Object[]{definition.getId(), pattern, filter});
}
return pattern && filter;
}
private boolean shouldTracePattern(ProcessorDefinition definition) {
for (String pattern : patterns) {
// match either route id, or node id
String id = definition.getId();
// use matchPattern method from endpoint helper that has a good matcher we use in Camel
if (EndpointHelper.matchPattern(id, pattern)) {
return true;
}
RouteDefinition route = ProcessorDefinitionHelper.getRoute(definition);
if (route != null) {
id = route.getId();
if (EndpointHelper.matchPattern(id, pattern)) {
return true;
}
}
}
// not matched the pattern
return false;
}
private boolean shouldTraceFilter(Exchange exchange) {
return predicate.matches(exchange);
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public int getBacklogSize() {
return backlogSize;
}
public void setBacklogSize(int backlogSize) {
if (backlogSize <= 0) {
throw new IllegalArgumentException("The backlog size must be a positive number, was: " + backlogSize);
}
if (backlogSize > MAX_BACKLOG_SIZE) {
throw new IllegalArgumentException("The backlog size cannot be greater than the max size of " + MAX_BACKLOG_SIZE + ", was: " + backlogSize);
}
this.backlogSize = backlogSize;
}
public boolean isRemoveOnDump() {
return removeOnDump;
}
public void setRemoveOnDump(boolean removeOnDump) {
this.removeOnDump = removeOnDump;
}
public int getBodyMaxChars() {
return bodyMaxChars;
}
public void setBodyMaxChars(int bodyMaxChars) {
this.bodyMaxChars = bodyMaxChars;
}
public boolean isBodyIncludeStreams() {
return bodyIncludeStreams;
}
public void setBodyIncludeStreams(boolean bodyIncludeStreams) {
this.bodyIncludeStreams = bodyIncludeStreams;
}
public boolean isBodyIncludeFiles() {
return bodyIncludeFiles;
}
public void setBodyIncludeFiles(boolean bodyIncludeFiles) {
this.bodyIncludeFiles = bodyIncludeFiles;
}
public String getTracePattern() {
return tracePattern;
}
public void setTracePattern(String tracePattern) {
this.tracePattern = tracePattern;
if (tracePattern != null) {
// the pattern can have multiple nodes separated by comma
this.patterns = tracePattern.split(",");
} else {
this.patterns = null;
}
}
public String getTraceFilter() {
return traceFilter;
}
public void setTraceFilter(String filter) {
this.traceFilter = filter;
if (filter != null) {
// assume simple language
String name = ObjectHelper.before(filter, ":");
if (name == null) {
// use simple language by default
name = "simple";
}
predicate = camelContext.resolveLanguage(name).createPredicate(filter);
}
}
public long getTraceCounter() {
return traceCounter.get();
}
public void resetTraceCounter() {
traceCounter.set(0);
}
public List dumpTracedMessages(String nodeId) {
List answer = new ArrayList();
if (nodeId != null) {
for (DefaultBacklogTracerEventMessage message : queue) {
if (nodeId.equals(message.getToNode()) || nodeId.equals(message.getRouteId())) {
answer.add(message);
}
}
}
if (removeOnDump) {
queue.removeAll(answer);
}
return answer;
}
public String dumpTracedMessagesAsXml(String nodeId) {
List events = dumpTracedMessages(nodeId);
StringBuilder sb = new StringBuilder();
sb.append("<").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
for (BacklogTracerEventMessage event : events) {
sb.append("\n").append(event.toXml(2));
}
sb.append("\n");
return sb.toString();
}
public List dumpAllTracedMessages() {
List answer = new ArrayList();
answer.addAll(queue);
if (isRemoveOnDump()) {
queue.clear();
}
return answer;
}
public String dumpAllTracedMessagesAsXml() {
List events = dumpAllTracedMessages();
StringBuilder sb = new StringBuilder();
sb.append("<").append(BacklogTracerEventMessage.ROOT_TAG).append("s>");
for (BacklogTracerEventMessage event : events) {
sb.append("\n").append(event.toXml(2));
}
sb.append("\n");
return sb.toString();
}
public void clear() {
queue.clear();
}
public long incrementTraceCounter() {
return traceCounter.incrementAndGet();
}
@Override
protected void doStart() throws Exception {
}
@Override
protected void doStop() throws Exception {
queue.clear();
}
}