001/* 002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * vpasquier <vpasquier@nuxeo.com> 018 * slacoin <slacoin@nuxeo.com> 019 */ 020package org.nuxeo.ecm.automation.core.trace; 021 022import java.util.Arrays; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.concurrent.TimeUnit; 027import java.util.function.Function; 028import org.apache.commons.logging.LogFactory; 029import org.nuxeo.ecm.automation.OperationCallback; 030import org.nuxeo.ecm.automation.OperationChain; 031import org.nuxeo.ecm.automation.OperationContext; 032import org.nuxeo.ecm.automation.OperationException; 033import org.nuxeo.ecm.automation.OperationType; 034import org.nuxeo.ecm.automation.core.impl.InvokableMethod; 035import org.nuxeo.runtime.api.Framework; 036 037import com.google.common.cache.Cache; 038import com.google.common.cache.CacheBuilder; 039 040/** 041 * @since 5.7.3 The Automation tracer factory service. 042 */ 043public class TracerFactory implements TracerFactoryMBean { 044 045 public static final String AUTOMATION_TRACE_PROPERTY = "org.nuxeo.automation.trace"; 046 047 public static final String AUTOMATION_TRACE_PRINTABLE_PROPERTY = "org.nuxeo.automation.trace.printable"; 048 049 protected static final Integer CACHE_CONCURRENCY_LEVEL = 10; 050 051 protected static final Integer CACHE_MAXIMUM_SIZE = 1000; 052 053 protected static final Integer CACHE_TIMEOUT = 10; 054 055 protected String printable; 056 057 protected Function<String,Boolean> printableAssertor; 058 059 protected Cache<String, ChainTraces> tracesCache; 060 061 protected boolean recording; 062 063 protected Trace lastError; 064 065 public TracerFactory() { 066 tracesCache = CacheBuilder.newBuilder().concurrencyLevel(CACHE_CONCURRENCY_LEVEL) 067 .maximumSize(CACHE_MAXIMUM_SIZE).expireAfterWrite(CACHE_TIMEOUT, TimeUnit.MINUTES).build(); 068 recording = Framework.isBooleanPropertyTrue(AUTOMATION_TRACE_PROPERTY); 069 setPrintableTraces(Framework.getProperty(AUTOMATION_TRACE_PRINTABLE_PROPERTY, "*")); 070 } 071 072 protected static class ChainTraces { 073 074 protected OperationType chain; 075 076 protected Map<Integer, Trace> traces = new HashMap<Integer, Trace>(); 077 078 protected ChainTraces(OperationType chain) { 079 this.chain = chain; 080 } 081 082 protected String add(Trace trace) { 083 int index = Integer.valueOf(traces.size()); 084 traces.put(Integer.valueOf(index), trace); 085 return formatKey(trace.chain, index); 086 } 087 088 protected Trace getTrace(int index) { 089 return traces.get(index); 090 } 091 092 protected void removeTrace(int index) { 093 traces.remove(index); 094 } 095 096 protected void clear() { 097 traces.clear(); 098 } 099 100 protected int size() { 101 return traces.size(); 102 } 103 104 } 105 106 /** 107 * If trace mode is enabled, instantiate {@link Tracer}. If not, instantiate {@link TracerLite}. 108 */ 109 public OperationCallback newTracer() { 110 return new Tracer(this); 111 } 112 113 public Call newCall(OperationType chain, OperationContext context, OperationType type, InvokableMethod method, 114 Map<String, Object> params) { 115 if (!recording) { 116 return new Call(chain, type); 117 } 118 return new Call(chain, context, type, method, params); 119 } 120 121 public Trace newTrace(Call parent, OperationType typeof, List<Call> calls, Object output, OperationException error) { 122 return new Trace(parent, typeof, calls, calls.get(0).details.input, output, error); 123 } 124 125 protected void recordTrace(Trace trace) { 126 String chainId = trace.chain.getId(); 127 ChainTraces chainTraces = tracesCache.getIfPresent(chainId); 128 if (chainTraces == null) { 129 tracesCache.put(chainId, new ChainTraces(trace.chain)); 130 } 131 if (trace.getError() != null) { 132 lastError = trace; 133 } 134 chainTraces = tracesCache.getIfPresent(chainId); 135 if (chainTraces.size() != 0) { 136 chainTraces.removeTrace(1); 137 } 138 tracesCache.getIfPresent(chainId).add(trace); 139 } 140 141 public Trace getTrace(OperationChain chain, int index) { 142 return tracesCache.getIfPresent(chain.getId()).getTrace(index); 143 } 144 145 /** 146 * @param key 147 * The name of the chain. 148 * @return The last trace of the given chain. 149 */ 150 public Trace getTrace(String key) { 151 return getTrace(key, -1); 152 } 153 154 public Trace getTrace(String key, int index) { 155 ChainTraces chainTrace = tracesCache.getIfPresent(key); 156 if (chainTrace == null) { 157 return null; 158 } 159 if (index < 0) { 160 index = chainTrace.traces.size() - 1; 161 } 162 return tracesCache.getIfPresent(key).getTrace(index); 163 } 164 165 public Trace getLastErrorTrace() { 166 return lastError; 167 } 168 169 public void clearTrace(OperationChain chain, int index) { 170 tracesCache.getIfPresent(chain).removeTrace(Integer.valueOf(index)); 171 } 172 173 public void clearTrace(OperationChain chain) { 174 tracesCache.invalidate(chain); 175 } 176 177 @Override 178 public void clearTraces() { 179 tracesCache.invalidateAll(); 180 } 181 182 protected static String formatKey(OperationType chain, int index) { 183 return String.format("%s:%s", chain.getId(), index); 184 } 185 186 public void onTrace(Trace trace) { 187 if (trace.error != null) { 188 trace.error.addSuppressed(new Throwable(print(trace))); 189 } 190 if (!recording) { 191 return; 192 } 193 if (printableAssertor.apply(trace.chain.getId())) { 194 LogFactory.getLog(Trace.class).debug(print(trace)); 195 } 196 recordTrace(trace); 197 } 198 199 @Override 200 public boolean toggleRecording() { 201 return recording = !recording; 202 } 203 204 @Override 205 public boolean getRecordingState() { 206 return recording; 207 } 208 209 @Override 210 public String getPrintableTraces() { 211 return printable; 212 } 213 214 @Override 215 public String setPrintableTraces(String option) { 216 if ("*".equals(option)) { 217 printableAssertor = s -> Boolean.TRUE; 218 } else { 219 List<String> patterns = Arrays.asList(option.split(",")); 220 printableAssertor = s -> { 221 return Boolean.valueOf(patterns.contains(s)); 222 }; 223 } 224 printable = option; 225 return printable; 226 } 227 228 public String print(Trace trace) { 229 return TracePrinter.print(trace, !recording); 230 } 231}