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<BacklogTracerEventMessage>(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<BacklogTracerEventMessage>(); 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<BacklogTracerEventMessage>(); 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}