package plume;
import java.util.Stack;
import java.io.PrintStream;
/**
* A logging class with the following features:
* <ul>
* <li>Can be enabled and disabled (when disabled, all operations are no-ops),
* <li>Write to a file or to standard output,
* <li>Can start and stop a timer, and nest timers,
* <li>Can enter and exit subtasks (their output is indented, and they are timed),
* <li>Can provide a backtrace (optionally provide a backtrace at every output), and
* <li>Can add newlines where appropriate, if variable line_oriented is set.
* </ul>
**/
public final class SimpleLog {
/** If false, do no output. */
public boolean enabled;
/** Where to write logging output. */
public PrintStream logfile = System.out;
/** The current indentation string. */
public String indent_str = "";
/** Indentation string for one level of indentation. */
public String indent_str_one_level = " ";
/** Always provide a backtrace (traceback) when calling log. */
public boolean always_traceback = false;
/**
* True if every log call is made with a complete line of text.
* False if a log call may contain multiple lines, or if multiple log
* calls may be made, each with parts of a line; in this case, you must
* manage line delimiters yourself.
*/
public boolean line_oriented = true;
public Stack<Long> start_times = new Stack<Long>();
public SimpleLog (boolean enabled, boolean always_traceback) {
this.enabled = enabled;
this.always_traceback = always_traceback;
push_start_time();
}
public SimpleLog (boolean enabled) {
this (enabled, false);
}
public SimpleLog() {
this (true);
}
public SimpleLog (String filename, boolean enabled) {
this (enabled);
try {
logfile = new PrintStream (filename);
} catch (Exception e) {
throw new RuntimeException ("Can't open " + filename, e);
}
}
public final boolean enabled() {
return enabled;
}
/**
* Log a message. Provide a backtrace (traceback) if variable
* always_traceback is set.
*/
public final void log (String format, /*@Nullable*/ Object... args) {
if (enabled) {
format = add_newline(format);
logfile.print (indent_str);
logfile.printf (format, args);
if (always_traceback)
tb();
}
}
/** Log a message, and provide a backtrace (traceback, or tb). */
public final void log_tb (String format, /*@Nullable*/ Object... args) {
if (enabled) {
log (format, args);
tb();
}
}
/** Print a backtrace (traceback, or tb) to the log. */
public final void tb() {
Throwable t = new Throwable();
t.fillInStackTrace();
StackTraceElement[] ste_arr = t.getStackTrace();
for (int ii = 2; ii < ste_arr.length; ii++) {
StackTraceElement ste = ste_arr[ii];
logfile.printf ("%s %s%n", indent_str, ste);
}
}
/**
* Helper method: add a newline if one isn't already there, and if
* variable line_oriented is set.
*/
private final String add_newline (String format) {
if (!line_oriented)
return format;
if (format.endsWith ("%n"))
return format;
return format + "%n";
}
///////////////////////////////////////////////////////////////////////////
/// Indentation
///
public final void indent() {
if (enabled) {
indent_str += indent_str_one_level;
push_start_time();
}
}
public final void indent (String format, /*@Nullable*/ Object... args) {
if (enabled) {
log (format, args);
indent();
}
}
/**
* Clears indent and start times and then pushes one start time
*/
public final void clear() {
if (enabled) {
indent_str = "";
start_times.clear();
push_start_time();
}
}
/**
* Calls clear() and then logs the specified message
*/
public final void clear (String format, /*@Nullable*/ Object... args) {
if (enabled) {
clear();
log (format, args);
}
}
public final void exdent() {
if (enabled) {
indent_str = indent_str.substring (0, indent_str.length()-indent_str_one_level.length());
pop_start_time();
}
}
/**
* Extents and <b>then</b> prints. This is confusing.
* @deprecated Use separate calls to {@link #exdent()} and
* {@link #log(String, Object...)}.
*/
@Deprecated
public final void exdent (String format, /*@Nullable*/ Object... args) {
if (enabled) {
exdent();
log (format, args);
}
}
/** Prints the time and then exdents. */
public final void exdent_time (String format, /*@Nullable*/ Object... args) {
if (enabled) {
// This puts the time inside, not outside, the indentation.
log_time (format, args);
exdent();
}
}
///////////////////////////////////////////////////////////////////////////
/// Timing
///
/**
* This overwrites the current start time; it does not push a new one!!
* @deprecated Use {@link #reset_start_time()}.
*/
@Deprecated
public final void start_time() {
reset_start_time();
}
/** This overwrites the current start time; it does not push a new one!! */
public final void reset_start_time() {
if (enabled) {
pop_start_time();
push_start_time();
}
}
/** Push a new start time onto the stack. */
public final void push_start_time() {
if (enabled)
start_times.push (System.currentTimeMillis());
}
public final void pop_start_time() {
start_times.pop();
}
/**
* Writes the specified message and the elapsed time since
* the last call to start_time().
* Does not pop nor reset the current start time.
*/
public final void log_time (String format, /*@Nullable*/ Object... args) {
if (enabled) {
Long start_time = start_times.peek();
if (start_time == null) {
throw new Error("Too many pops before calling log_time");
}
long elapsed = System.currentTimeMillis() - start_time.longValue();
logfile.print (indent_str);
if (elapsed > 1000)
logfile.printf ("[%,f secs] ", elapsed/1000.0);
else
logfile.print ("[" + elapsed + " ms] ");
format = add_newline(format);
logfile.printf (format, args);
}
}
}