package org.springframework.roo.metadata;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.metadata.internal.StandardMetadataTimingStatistic;
/**
* Default implementation of {@link MetadataLogger}.
*
* @author Ben Alex
* @since 1.1.2
*/
@Service
@Component
public class DefaultMetadataLogger implements MetadataLogger {
private static class TimerEntry {
long clockStartedOrResumed; // nanos
long duration; // nanos
String responsibleClass;
}
private long eventNumber = 0;
private final Stack<Long> eventStack = new Stack<Long>();
private FileWriter fileLog;
/**
* key: responsible class, value: number of times a timing record was
* created for the responsible class
*/
private final Map<String, Long> invocations = new HashMap<String, Long>();
private final Class<DefaultMetadataLogger> mutex = DefaultMetadataLogger.class;
private final Stack<TimerEntry> timerStack = new Stack<TimerEntry>();
/** key: responsible class, value: nanos occupied */
private final Map<String, Long> timings = new HashMap<String, Long>();
private int traceLevel = 0;
public DefaultMetadataLogger() {
if (System.getProperty("roo.metadata.trace") != null) {
traceLevel = 2;
}
}
public SortedSet<MetadataTimingStatistic> getTimings() {
final SortedSet<MetadataTimingStatistic> result = new TreeSet<MetadataTimingStatistic>();
synchronized (mutex) {
for (final String key : timings.keySet()) {
result
.add(new StandardMetadataTimingStatistic(key, timings.get(key), invocations.get(key)));
}
}
return result;
}
public int getTraceLevel() {
return traceLevel;
}
public void log(final String message) {
Validate.notBlank(message, "Message to log required");
Validate.isTrue(eventStack.size() > 0,
"Event stack is empty, so no logging should have been requested at this time");
final StringBuilder sb = new StringBuilder("00000000");
// Get the current event ID off the stack
final Long eventIdentifier = eventStack.get(eventStack.size() - 1);
// Figure out the indentation level
final int indentationLevel = eventStack.size();
final String hex = Long.toHexString(eventIdentifier);
sb.replace(8 - hex.length(), 8, hex);
for (int i = 0; i < indentationLevel; i++) {
sb.append(" ");
}
sb.append(message);
logToFile(sb.toString());
}
private void logToFile(final String line) {
if (fileLog == null) {
try {
// Overwrite existing (don't append)
fileLog = new FileWriter("metadata.log", false);
} catch (final IOException ignore) {
}
if (fileLog == null) {
// Still failing, so give up
return;
}
}
try {
fileLog.write(line + "\n"); // Unix line endings only from Roo
fileLog.flush(); // So tail -f will show it's working
} catch (final IOException ignoreIt) {
}
}
public void setTraceLevel(final int trace) {
traceLevel = trace;
}
public void startEvent() {
eventNumber++;
eventStack.push(eventNumber);
}
public void startTimer(final String responsibleClass) {
Validate.notBlank(responsibleClass, "Responsible class required");
final long now = System.nanoTime();
if (timerStack.size() > 0) {
// There is an existing timer on the stack, so we need to stop the
// clock for it
final TimerEntry timerEntry = timerStack.get(timerStack.size() - 1);
// Add the duration it ran to any existing duration
timerEntry.duration = timerEntry.duration + now - timerEntry.clockStartedOrResumed;
timerEntry.clockStartedOrResumed = now;
}
// Start a new timer
final TimerEntry timerEntry = new TimerEntry();
timerEntry.responsibleClass = responsibleClass;
timerEntry.clockStartedOrResumed = now;
timerStack.push(timerEntry);
}
public void stopEvent() {
Validate.isTrue(eventStack.size() > 0,
"Event stack is empty, indicating a mismatched number of timer start/stop calls");
eventStack.pop();
}
public void stopTimer() {
Validate.isTrue(timerStack.size() > 0,
"Timer stack is empty, indicating a mismatched number of timer start/stop calls");
final long now = System.nanoTime();
final TimerEntry timerEntry = timerStack.pop();
timerEntry.duration = timerEntry.duration + now - timerEntry.clockStartedOrResumed;
final String responsibleClass = timerEntry.responsibleClass;
// Update the timings summary
synchronized (mutex) {
Long existingSummary = timings.get(responsibleClass);
if (existingSummary == null) {
existingSummary = timerEntry.duration;
} else {
existingSummary = existingSummary + timerEntry.duration;
}
timings.put(responsibleClass, existingSummary);
// Update the invocation count
Long existingInvocations = invocations.get(responsibleClass);
if (existingInvocations == null) {
existingInvocations = 0L;
}
existingInvocations++;
invocations.put(responsibleClass, existingInvocations);
}
}
}