/* * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed 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. * * Contributors: * vpasquier <vpasquier@nuxeo.com> * slacoin <slacoin@nuxeo.com> */ package org.nuxeo.ecm.automation.core.trace; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.automation.OperationCallback; import org.nuxeo.ecm.automation.OperationChain; import org.nuxeo.ecm.automation.OperationContext; import org.nuxeo.ecm.automation.OperationException; import org.nuxeo.ecm.automation.OperationType; import org.nuxeo.ecm.automation.core.impl.InvokableMethod; import org.nuxeo.runtime.api.Framework; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; /** * @since 5.7.3 The Automation tracer factory service. */ public class TracerFactory implements TracerFactoryMBean { public static final String AUTOMATION_TRACE_PROPERTY = "org.nuxeo.automation.trace"; public static final String AUTOMATION_TRACE_PRINTABLE_PROPERTY = "org.nuxeo.automation.trace.printable"; protected static final Integer CACHE_CONCURRENCY_LEVEL = 10; protected static final Integer CACHE_MAXIMUM_SIZE = 1000; protected static final Integer CACHE_TIMEOUT = 10; protected String printable; protected Function<String,Boolean> printableAssertor; protected Cache<String, ChainTraces> tracesCache; protected boolean recording; protected Trace lastError; public TracerFactory() { tracesCache = CacheBuilder.newBuilder().concurrencyLevel(CACHE_CONCURRENCY_LEVEL) .maximumSize(CACHE_MAXIMUM_SIZE).expireAfterWrite(CACHE_TIMEOUT, TimeUnit.MINUTES).build(); recording = Framework.isBooleanPropertyTrue(AUTOMATION_TRACE_PROPERTY); setPrintableTraces(Framework.getProperty(AUTOMATION_TRACE_PRINTABLE_PROPERTY, "*")); } protected static class ChainTraces { protected OperationType chain; protected Map<Integer, Trace> traces = new HashMap<Integer, Trace>(); protected ChainTraces(OperationType chain) { this.chain = chain; } protected String add(Trace trace) { int index = Integer.valueOf(traces.size()); traces.put(Integer.valueOf(index), trace); return formatKey(trace.chain, index); } protected Trace getTrace(int index) { return traces.get(index); } protected void removeTrace(int index) { traces.remove(index); } protected void clear() { traces.clear(); } protected int size() { return traces.size(); } } /** * If trace mode is enabled, instantiate {@link Tracer}. If not, instantiate {@link TracerLite}. */ public OperationCallback newTracer() { return new Tracer(this); } public Call newCall(OperationType chain, OperationContext context, OperationType type, InvokableMethod method, Map<String, Object> params) { if (!recording) { return new Call(chain, type); } return new Call(chain, context, type, method, params); } public Trace newTrace(Call parent, OperationType typeof, List<Call> calls, Object output, OperationException error) { return new Trace(parent, typeof, calls, calls.get(0).details.input, output, error); } protected void recordTrace(Trace trace) { String chainId = trace.chain.getId(); ChainTraces chainTraces = tracesCache.getIfPresent(chainId); if (chainTraces == null) { tracesCache.put(chainId, new ChainTraces(trace.chain)); } if (trace.getError() != null) { lastError = trace; } chainTraces = tracesCache.getIfPresent(chainId); if (chainTraces.size() != 0) { chainTraces.removeTrace(1); } tracesCache.getIfPresent(chainId).add(trace); } public Trace getTrace(OperationChain chain, int index) { return tracesCache.getIfPresent(chain.getId()).getTrace(index); } /** * @param key * The name of the chain. * @return The last trace of the given chain. */ public Trace getTrace(String key) { return getTrace(key, -1); } public Trace getTrace(String key, int index) { ChainTraces chainTrace = tracesCache.getIfPresent(key); if (chainTrace == null) { return null; } if (index < 0) { index = chainTrace.traces.size() - 1; } return tracesCache.getIfPresent(key).getTrace(index); } public Trace getLastErrorTrace() { return lastError; } public void clearTrace(OperationChain chain, int index) { tracesCache.getIfPresent(chain).removeTrace(Integer.valueOf(index)); } public void clearTrace(OperationChain chain) { tracesCache.invalidate(chain); } @Override public void clearTraces() { tracesCache.invalidateAll(); } protected static String formatKey(OperationType chain, int index) { return String.format("%s:%s", chain.getId(), index); } public void onTrace(Trace trace) { if (trace.error != null) { trace.error.addSuppressed(new Throwable(print(trace))); } if (!recording) { return; } if (printableAssertor.apply(trace.chain.getId())) { LogFactory.getLog(Trace.class).debug(print(trace)); } recordTrace(trace); } @Override public boolean toggleRecording() { return recording = !recording; } @Override public boolean getRecordingState() { return recording; } @Override public String getPrintableTraces() { return printable; } @Override public String setPrintableTraces(String option) { if ("*".equals(option)) { printableAssertor = s -> Boolean.TRUE; } else { List<String> patterns = Arrays.asList(option.split(",")); printableAssertor = s -> { return Boolean.valueOf(patterns.contains(s)); }; } printable = option; return printable; } public String print(Trace trace) { return TracePrinter.print(trace, !recording); } }