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; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.nuxeo.ecm.automation.OperationCallback; 032import org.nuxeo.ecm.automation.OperationChain; 033import org.nuxeo.ecm.automation.OperationContext; 034import org.nuxeo.ecm.automation.OperationException; 035import org.nuxeo.ecm.automation.OperationType; 036import org.nuxeo.ecm.automation.core.impl.InvokableMethod; 037import org.nuxeo.runtime.api.Framework; 038 039import com.google.common.cache.Cache; 040import com.google.common.cache.CacheBuilder; 041 042/** 043 * @since 5.7.3 The Automation tracer factory service. 044 */ 045public class TracerFactory implements TracerFactoryMBean { 046 047 public static final String AUTOMATION_TRACE_PROPERTY = "org.nuxeo.automation.trace"; 048 049 public static final String AUTOMATION_TRACE_PRINTABLE_PROPERTY = "org.nuxeo.automation.trace.printable"; 050 051 protected static final Integer CACHE_CONCURRENCY_LEVEL = 10; 052 053 protected static final Integer CACHE_MAXIMUM_SIZE = 1000; 054 055 protected static final Integer CACHE_TIMEOUT = 10; 056 057 protected String printable; 058 059 protected Function<String, Boolean> printableAssertor; 060 061 protected Cache<String, ChainTraces> tracesCache; 062 063 protected boolean recording; 064 065 protected Trace lastError; 066 067 public TracerFactory() { 068 tracesCache = CacheBuilder.newBuilder() 069 .concurrencyLevel(CACHE_CONCURRENCY_LEVEL) 070 .maximumSize(CACHE_MAXIMUM_SIZE) 071 .expireAfterWrite(CACHE_TIMEOUT, TimeUnit.MINUTES) 072 .build(); 073 recording = Framework.isBooleanPropertyTrue(AUTOMATION_TRACE_PROPERTY); 074 setPrintableTraces(Framework.getProperty(AUTOMATION_TRACE_PRINTABLE_PROPERTY, "*")); 075 } 076 077 protected static class ChainTraces { 078 079 protected OperationType chain; 080 081 protected Map<Integer, Trace> traces = new HashMap<>(); 082 083 protected ChainTraces(OperationType chain) { 084 this.chain = chain; 085 } 086 087 protected String add(Trace trace) { 088 int index = traces.size(); 089 traces.put(Integer.valueOf(index), trace); 090 return formatKey(trace.chain, index); 091 } 092 093 protected Trace getTrace(int index) { 094 return traces.get(index); 095 } 096 097 protected void removeTrace(int index) { 098 traces.remove(index); 099 } 100 101 protected void clear() { 102 traces.clear(); 103 } 104 105 protected int size() { 106 return traces.size(); 107 } 108 109 } 110 111 public OperationCallback newTracer() { 112 return new Tracer(this); 113 } 114 115 /** 116 * If trace mode is enabled, instantiate {@link Call}. If not, instantiate {@link LiteCall}. 117 */ 118 public Call newCall(OperationType chain, OperationContext context, OperationType type, InvokableMethod method, 119 Map<String, Object> params) { 120 if (!recording) { 121 return new LiteCall(chain, type); 122 } 123 return new Call(chain, context, type, method, params); 124 } 125 126 public Trace newTrace(Call parent, OperationType typeof, List<Call> calls, Object output, 127 OperationException error) { 128 return new Trace(parent, typeof, calls, calls.get(0).details.input, output, error); 129 } 130 131 protected void recordTrace(Trace trace) { 132 String chainId = trace.chain.getId(); 133 ChainTraces chainTraces = tracesCache.getIfPresent(chainId); 134 if (chainTraces == null) { 135 tracesCache.put(chainId, new ChainTraces(trace.chain)); 136 } 137 if (trace.getError() != null) { 138 lastError = trace; 139 } 140 chainTraces = tracesCache.getIfPresent(chainId); 141 if (chainTraces.size() != 0) { 142 chainTraces.removeTrace(1); 143 } 144 tracesCache.getIfPresent(chainId).add(trace); 145 } 146 147 public Trace getTrace(OperationChain chain, int index) { 148 return tracesCache.getIfPresent(chain.getId()).getTrace(index); 149 } 150 151 /** 152 * @param key The name of the chain. 153 * @return The last trace of the given chain. 154 */ 155 public Trace getTrace(String key) { 156 return getTrace(key, -1); 157 } 158 159 public Trace getTrace(String key, int index) { 160 ChainTraces chainTrace = tracesCache.getIfPresent(key); 161 if (chainTrace == null) { 162 return null; 163 } 164 if (index < 0) { 165 index = chainTrace.traces.size() - 1; 166 } 167 return tracesCache.getIfPresent(key).getTrace(index); 168 } 169 170 public Trace getLastErrorTrace() { 171 return lastError; 172 } 173 174 public void clearTrace(OperationChain chain, int index) { 175 tracesCache.getIfPresent(chain).removeTrace(index); 176 } 177 178 public void clearTrace(OperationChain chain) { 179 tracesCache.invalidate(chain); 180 } 181 182 @Override 183 public void clearTraces() { 184 tracesCache.invalidateAll(); 185 } 186 187 protected static String formatKey(OperationType chain, int index) { 188 return String.format("%s:%s", chain.getId(), index); 189 } 190 191 public void onTrace(Trace trace) { 192 boolean containsError = trace.error != null; 193 if (!(recording || containsError)) { 194 return; 195 } 196 197 if (containsError) { 198 trace.error.addSuppressed(new Throwable(print(trace))); 199 } 200 if (printableAssertor.apply(trace.chain.getId())) { 201 Log log = LogFactory.getLog(Trace.class); 202 if (containsError) { 203 log.warn(print(trace)); 204 } else { 205 log.info(print(trace)); 206 } 207 } 208 recordTrace(trace); 209 } 210 211 @Override 212 public boolean toggleRecording() { 213 return recording = !recording; 214 } 215 216 @Override 217 public boolean getRecordingState() { 218 return recording; 219 } 220 221 @Override 222 public String getPrintableTraces() { 223 return printable; 224 } 225 226 @Override 227 public String setPrintableTraces(String option) { 228 if ("*".equals(option)) { 229 printableAssertor = s -> Boolean.TRUE; 230 } else { 231 List<String> patterns = Arrays.asList(option.split(",")); 232 printableAssertor = s -> { 233 return Boolean.valueOf(patterns.contains(s)); 234 }; 235 } 236 printable = option; 237 return printable; 238 } 239 240 public String print(Trace trace) { 241 return trace.print(!recording); 242 } 243}