edu.stanford.nlp.util.logging.Redwood Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of stanford-parser Show documentation
Show all versions of stanford-parser Show documentation
Stanford Parser processes raw text in English, Chinese, German, Arabic, and French, and extracts constituency parse trees.
package edu.stanford.nlp.util.logging;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import edu.stanford.nlp.util.Execution;
import edu.stanford.nlp.util.Generics;
import edu.stanford.nlp.util.IterableIterator;
/**
* A hierarchical channel based logger. Log messages are arranged hierarchically by depth
* (e.g. main->tagging->sentence 2) using the startTrack() and endTrack() methods.
* Furthermore, messages can be flagged with a number of channels, which allow filtering by channel.
* Log levels are implemented as channels (ERROR, WARNING, etc).
*
* Details on the handlers used are documented in their respective classes, which all implement
* {@link LogRecordHandler}.
* New handlers should implement this class.
*
* Details on configuring Redwood can be found in the {@link RedwoodConfiguration} class.
* New configuration methods should be implemented in this class, following the standard
* builder paradigm.
*
* There is a tutorial on Redwood on the
* NLP website.
*
* @author Gabor Angeli (angeli at cs.stanford)
* @author David McClosky
*/
public class Redwood {
/*
---------------------------------------------------------
VARIABLES
---------------------------------------------------------
*/
// -- UTILITIES --
public static final Flag ERR = Flag.ERROR;
public static final Flag WARN = Flag.WARN;
public static final Flag DBG = Flag.DEBUG;
public static final Flag FORCE = Flag.FORCE;
public static final Flag STDOUT = Flag.STDOUT;
public static final Flag STDERR = Flag.STDERR;
// -- STREAMS --
/**
* The real System.out stream
*/
protected static final PrintStream realSysOut = System.out;
/**
* The real System.err stream
*/
protected static final PrintStream realSysErr = System.err;
// -- BASIC LOGGING --
/**
* The tree of handlers
*/
private static RecordHandlerTree handlers = new RecordHandlerTree();
/**
* The current depth of the logger
*/
private static int depth = 0;
/**
* The stack of track titles, for consistency checking
* the endTrack() call
*/
private static final Stack titleStack = new Stack();
/**
* Signals that no more log messages should be accepted by Redwood
*/
private static boolean isClosed = false;
// -- THREADED ENVIRONMENT --
/**
* Queue of tasks to be run in various threads
*/
private static final Map> threadedLogQueue = Generics.newHashMap();
/**
* Thread id which currently has control of the Redwood
*/
private static long currentThread = -1L;
/**
* Threads which have something they wish to log, but do not yet
* have control of Redwood
*/
private static final Queue threadsWaiting = new LinkedList();
/**
* Indicator that messages are coming from multiple threads
*/
private static boolean isThreaded = false;
/**
* Synchronization
*/
private static final ReentrantLock control = new ReentrantLock();
private Redwood() {} // static class
/*
---------------------------------------------------------
HELPER METHODS
---------------------------------------------------------
*/
private static void queueTask(long threadId, Runnable toRun){
assert control.isHeldByCurrentThread();
assert threadId != currentThread;
//(get queue)
if(!threadedLogQueue.containsKey(threadId)){
threadedLogQueue.put(threadId, new LinkedList());
}
Queue threadLogQueue = threadedLogQueue.get(threadId);
//(add to queue)
threadLogQueue.offer( toRun );
//(register this thread as waiting)
if(!threadsWaiting.contains(threadId)){
threadsWaiting.offer(threadId);
assert threadedLogQueue.get(threadId) != null && !threadedLogQueue.get(threadId).isEmpty();
}
}
private static void releaseThreadControl(long threadId){
assert !isThreaded || control.isHeldByCurrentThread();
assert currentThread < 0L || currentThread == threadId;
//(release control)
currentThread = -1L;
}
private static void attemptThreadControl(long threadId, Runnable r){
//(get lock)
boolean tookLock = false;
if(!control.isHeldByCurrentThread()){
control.lock();
tookLock = true;
}
//(perform action)
attemptThreadControlThreadsafe(threadId);
if(threadId == currentThread){
r.run();
} else {
queueTask(threadId, r);
}
//(release lock)
assert control.isHeldByCurrentThread();
if(tookLock){
control.unlock();
}
}
private static void attemptThreadControlThreadsafe(long threadId){
//--Assertions
assert control.isHeldByCurrentThread();
//--Update Current Thread
boolean hopeless = true;
if(currentThread < 0L){
//(case: no one has control)
if(threadsWaiting.isEmpty()){
currentThread = threadId;
} else {
currentThread = threadsWaiting.poll();
hopeless = false;
assert threadedLogQueue.get(currentThread) == null || !threadedLogQueue.get(currentThread).isEmpty();
}
} else if(currentThread == threadId) {
//(case: we have control)
threadsWaiting.remove(currentThread);
} else if(currentThread >= 0L){
//(case: someone else has control
threadsWaiting.remove(currentThread);
} else {
assert false;
}
//--Clear Backlog
long activeThread = currentThread;
Queue backlog = threadedLogQueue.get(currentThread);
if(backlog != null){
//(run backlog)
while(!backlog.isEmpty() && currentThread >= 0L){
backlog.poll().run();
}
//(requeue, if applicable)
if(currentThread < 0L && !backlog.isEmpty()){
threadsWaiting.offer(activeThread);
hopeless = false;
}
}
//--Recursive
if(!hopeless && currentThread != threadId){
attemptThreadControlThreadsafe(threadId);
}
assert !threadsWaiting.contains(currentThread);
assert control.isHeldByCurrentThread();
}
protected static RecordHandlerTree rootHandler() {
return handlers;
}
/**
* Remove all log handlers from Redwood, presumably in order to
* construct a custom pipeline afterwards
*/
protected static void clearHandlers(){
handlers = new RecordHandlerTree();
}
/**
* Get a handler based on its class
* @param clazz The class of the Handler to return.
* If multiple Handlers exist, the first one is returned.
* @param The class of the handler to return.
* @return The handler matching the class name.
*/
@Deprecated
@SuppressWarnings("unchecked")
private static E getHandler(Class clazz){
for(LogRecordHandler cand : handlers){
if(clazz == cand.getClass()){
return (E) cand;
}
}
return null;
}
/**
* Captures System.out and System.err and redirects them
* to Redwood logging.
* @param captureOut True is System.out should be captured
* @param captureErr True if System.err should be captured
*/
protected static void captureSystemStreams(boolean captureOut, boolean captureErr){
if(captureOut){
System.setOut(new RedwoodPrintStream(STDOUT, realSysOut));
} else {
System.setOut(realSysOut);
}
if(captureErr){
System.setErr(new RedwoodPrintStream(STDERR, realSysErr));
} else {
System.setErr(realSysErr);
}
}
/**
* Restores System.out and System.err to their original values
*/
protected static void restoreSystemStreams(){
System.setOut(realSysOut);
System.setErr(realSysErr);
}
/*
---------------------------------------------------------
TRUE PUBLIC FACING METHODS
---------------------------------------------------------
*/
/**
* Log a message. The last argument to this object is the message to log
* (usually a String); the first arguments are the channels to log to.
*
* For example:
*
* log(Redwood.ERR,"tag","this message is tagged with ERROR and tag")
*
* @param args The last argument is the message; the first arguments are the channels.
*/
public static void log(Object... args) {
//--Argument Check
if(args.length == 0){ return; }
if(isClosed){ return; }
//--Create Record
final Object content = args[args.length-1];
final Object[] tags = new Object[args.length-1];
System.arraycopy(args,0,tags,0,args.length-1);
final long timestamp = System.currentTimeMillis();
//--Handle Record
if(isThreaded){
//(case: multithreaded)
final Runnable log = () -> {
assert !isThreaded || control.isHeldByCurrentThread();
Record toPass = new Record(content,tags,depth,timestamp);
handlers.process(toPass, MessageType.SIMPLE,depth, toPass.timesstamp);
assert !isThreaded || control.isHeldByCurrentThread();
};
long threadId = Thread.currentThread().getId();
attemptThreadControl( threadId, log );
} else {
//(case: no threading)
Record toPass = new Record(content,tags,depth,timestamp);
handlers.process(toPass, MessageType.SIMPLE,depth, toPass.timesstamp);
}
}
/**
* The Redwood equivalent to printf().
* @param format The format string, as per java's Formatter.format() object.
* @param args The arguments to format.
*/
public static void logf(String format, Object... args){ log(new Formatter().format(format, args)); }
/**
* Begin a "track;" that is, begin logging at one level deeper.
* Channels other than the FORCE channel are ignored.
* @param args The title of the track to begin, with an optional FORCE flag.
*/
public static void startTrack(final Object... args){
if(isClosed){ return; }
//--Create Record
final int len = args.length == 0 ? 0 : args.length-1;
final Object content = args.length == 0 ? "" : args[len];
final Object[] tags = new Object[len];
final long timestamp = System.currentTimeMillis();
System.arraycopy(args,0,tags,0,len);
//--Create Task
final Runnable startTrack = new Runnable(){
@Override
public void run(){
assert !isThreaded || control.isHeldByCurrentThread();
Record toPass = new Record(content,tags,depth,timestamp);
depth += 1;
titleStack.push(args.length == 0 ? "" : args[len].toString());
handlers.process(toPass, MessageType.START_TRACK, depth, toPass.timesstamp);
assert !isThreaded || control.isHeldByCurrentThread();
}
};
//--Run Task
if(isThreaded){
//(case: multithreaded)
long threadId = Thread.currentThread().getId();
attemptThreadControl( threadId, startTrack );
} else {
//(case: no threading)
startTrack.run();
}
}
/**
* Helper method to start a track on the FORCE channel.
* @param name The track name to print
*/
public static void forceTrack(Object name) {
startTrack(FORCE, name);
}
/**
* Helper method to start an anonymous track on the FORCE channel.
*/
public static void forceTrack() {
startTrack(FORCE, "");
}
/**
* End a "track;" that is, return to logging at one level shallower.
* @param title A title that should match the beginning of this track.
*/
public static void endTrack(final String title){
if(isClosed){ return; }
//--Make Task
final long timestamp = System.currentTimeMillis();
Runnable endTrack = () -> {
assert !isThreaded || control.isHeldByCurrentThread();
String expected = titleStack.pop();
//(check name match)
if (!isThreaded && !expected.equalsIgnoreCase(title)){
throw new IllegalArgumentException("Track names do not match: expected: " + expected + " found: " + title);
}
//(decrement depth)
depth -= 1;
//(send signal)
handlers.process(null, MessageType.END_TRACK, depth, timestamp);
assert !isThreaded || control.isHeldByCurrentThread();
};
//--Run Task
if(isThreaded){
//(case: multithreaded)
long threadId = Thread.currentThread().getId();
attemptThreadControl( threadId, endTrack );
} else {
//(case: no threading)
endTrack.run();
}
}
/**
* A utility method for closing calls to the anonymous startTrack() call.
*/
public static void endTrack(){ endTrack(""); }
/**
* Start a multithreaded logging environment. Log messages will be real time
* from one of the threads; as each thread finishes, another thread begins logging,
* first by making up the backlog, and then by printing any new log messages.
* A thread signals that it has finished logging with the finishThread() function;
* the multithreaded environment is ended with the endThreads() function
* @param title The name of the thread group being started
*/
public static void startThreads(String title){
if(isThreaded){
throw new IllegalStateException("Cannot nest Redwood threaded environments");
}
startTrack(FORCE,"Threads( "+title+" )");
isThreaded = true;
}
/**
* Signal that this thread will not log any more messages in the multithreaded
* environment
*/
public static void finishThread(){
//--Create Task
final long threadId = Thread.currentThread().getId();
Runnable finish = () -> releaseThreadControl(threadId);
//--Run Task
if(isThreaded){
//(case: multithreaded)
attemptThreadControl( threadId, finish );
} else {
//(case: no threading)
Redwood.log(Flag.WARN, "finishThreads() called outside of threaded environment");
}
}
/**
* Signal that all threads have run to completion, and the multithreaded
* environment is over.
* @param check The name of the thread group passed to startThreads()
*/
public static void endThreads(String check){
//(error check)
isThreaded = false;
if(currentThread != -1L){
Redwood.log(Flag.WARN, "endThreads() called, but thread " + currentThread + " has not finished (exception in thread?)");
}
//(end threaded environment)
assert !control.isHeldByCurrentThread();
//(write remaining threads)
boolean cleanPass = false;
while(!cleanPass){
cleanPass = true;
for(long thread : threadedLogQueue.keySet()){
assert currentThread < 0L;
if(threadedLogQueue.get(thread) != null && !threadedLogQueue.get(thread).isEmpty()){
//(mark queue as unclean)
cleanPass = false;
//(variables)
Queue backlog = threadedLogQueue.get(thread);
currentThread = thread;
//(clear buffer)
while(currentThread >= 0){
if(backlog.isEmpty()){ Redwood.log(Flag.WARN, "Forgot to call finishThread() on thread " + currentThread); }
assert !control.isHeldByCurrentThread();
backlog.poll().run();
}
//(unregister thread)
threadsWaiting.remove(thread);
}
}
}
while(threadsWaiting.size() > 0){
assert currentThread < 0L;
assert control.tryLock();
assert !threadsWaiting.isEmpty();
control.lock();
attemptThreadControlThreadsafe(-1);
control.unlock();
}
//(clean up)
for(Map.Entry> longQueueEntry : threadedLogQueue.entrySet()){
assert longQueueEntry.getValue().isEmpty();
}
assert threadsWaiting.isEmpty();
assert currentThread == -1L;
endTrack("Threads( "+check+" )");
}
/**
* Create an object representing a group of channels.
* {@link RedwoodChannels} contains a more complete description.
*
* @see RedwoodChannels
*/
public static RedwoodChannels channels(Object... channelNames) {
return new RedwoodChannels(channelNames);
}
/**
* Hide multiple channels. All other channels will be unaffected.
* @param channels The channels to hide
*/
public static void hideChannelsEverywhere(Object... channels){
for(LogRecordHandler handler : handlers){
if(handler instanceof VisibilityHandler){
VisibilityHandler visHandler = (VisibilityHandler) handler;
for (Object channel : channels) {
visHandler.alsoHide(channel);
}
}
}
}
/**
* Stop Redwood, closing all tracks and prohibiting future log messages.
*/
public static void stop(){
//--Close logger
isClosed = true; // <- not a thread-safe boolean
Thread.yield(); //poor man's synchronization attempt (let everything else log that wants to)
Thread.yield();
//--Close Tracks
while(depth > 0){
depth -= 1;
//(send signal to handlers)
handlers.process(null, MessageType.END_TRACK, depth, System.currentTimeMillis());
}
//--Shutdown
handlers.process(null, MessageType.SHUTDOWN, 0, System.currentTimeMillis());
}
/*
---------------------------------------------------------
UTILITY METHODS
---------------------------------------------------------
*/
/**
* Utility method for formatting a time difference (maybe this should go to a util class?)
* @param diff Time difference in milliseconds
* @param b The string builder to append to
*/
protected static void formatTimeDifference(long diff, StringBuilder b){
//--Get Values
int mili = (int) diff % 1000;
long rest = diff / 1000;
int sec = (int) rest % 60;
rest = rest / 60;
int min = (int) rest % 60;
rest = rest / 60;
int hr = (int) rest % 24;
rest = rest / 24;
int day = (int) rest;
//--Make String
if(day > 0) b.append(day).append(day > 1 ? " days, " : " day, ");
if(hr > 0) b.append(hr).append(hr > 1 ? " hours, " : " hour, ");
if(min > 0) {
if(min < 10){ b.append("0"); }
b.append(min).append(":");
}
if(min > 0 && sec < 10){ b.append("0"); }
b.append(sec).append(".").append(mili);
if(min > 0) b.append(" minutes");
else b.append(" seconds");
}
public static String formatTimeDifference(long diff){
StringBuilder b = new StringBuilder();
formatTimeDifference(diff, b);
return b.toString();
}
public static final boolean supportsAnsi;
static {
String os = System.getProperty("os.name").toLowerCase();
boolean isUnix = os.contains("unix") || os.contains("linux") || os.contains("solaris");
supportsAnsi = Boolean.getBoolean("Ansi") || isUnix;
}
/**
* Set up the default logger.
*/
static {
RedwoodConfiguration.standard().apply();
}
/**
* An enumeration of the types of "messages" you can send a handler
*/
private static enum MessageType{ SIMPLE, START_TRACK, SHUTDOWN, END_TRACK }
/**
* A tree structure of record handlers
*/
protected static class RecordHandlerTree implements Iterable{
// -- Overhead --
private final boolean isRoot;
private final LogRecordHandler head;
private final List children = new ArrayList();
public RecordHandlerTree() {
isRoot = true;
head = null;
}
public RecordHandlerTree(LogRecordHandler head) {
this.isRoot = false;
this.head = head;
}
// -- Core Tree Methods --
public LogRecordHandler head(){
return head;
}
public Iterator children(){
return children.iterator();
}
// -- Utility Methods --
public void addChild(LogRecordHandler handler){
if(Redwood.depth != 0){
throw new IllegalStateException("Cannot modify Redwood when within a track");
}
children.add(new RecordHandlerTree(handler));
}
protected void addChildTree(RecordHandlerTree tree){
if(Redwood.depth != 0){
throw new IllegalStateException("Cannot modify Redwood when within a track");
}
children.add(tree);
}
public LogRecordHandler removeChild(LogRecordHandler handler){
if(Redwood.depth != 0){
throw new IllegalStateException("Cannot modify Redwood when within a track");
}
Iterator iter = children();
while(iter.hasNext()){
LogRecordHandler cand = iter.next().head();
if(cand == handler){
iter.remove();
return cand;
}
}
return null;
}
public RecordHandlerTree find(LogRecordHandler toFind){
if(toFind == head()){
return this;
} else {
Iterator iter = children();
while(iter.hasNext()){
RecordHandlerTree cand = iter.next().find(toFind);
if(cand != null){ return cand; }
}
}
return null;
}
@Override
public Iterator iterator() {
return new Iterator(){
// -- Variables
private boolean seenHead = isRoot;
private final Iterator childrenIter = children();
private final RecordHandlerTree childOnPrix = childrenIter.hasNext() ? childrenIter.next() : null;
private Iterator childIter = childOnPrix == null ? null : childOnPrix.iterator();
private LogRecordHandler lastReturned = null;
// -- HasNext
@Override
public boolean hasNext() {
while(childIter != null && !childIter.hasNext()){
if(!childrenIter.hasNext()) {
break;
} else {
childIter = childrenIter.next().iterator();
}
}
return !seenHead || (childIter != null && childIter.hasNext());
}
// -- Next
@Override
public LogRecordHandler next() {
if(!seenHead){ seenHead = true; return head(); }
lastReturned = childIter.next();
return lastReturned;
}
// -- Remove
@Override
public void remove() {
if(!seenHead){ throw new IllegalStateException("INTERNAL: this shouldn't happen..."); }
if(lastReturned == null){ throw new IllegalStateException("Called remove() before any elements returned"); }
if(childOnPrix != null && lastReturned == childOnPrix.head()){
childrenIter.remove();
} else if(childIter != null){
childIter.remove();
} else {
throw new IllegalStateException("INTERNAL: not sure what we're removing");
}
}
};
}
private static List append(List lst, Record toAppend){
if(lst == LogRecordHandler.EMPTY){
lst = new ArrayList();
}
lst.add(toAppend);
return lst;
}
private void process(Record toPass, MessageType type, int newDepth, long timestamp){
//--Handle Message
//(records to pass on)
List toPassOn;
if(head != null){
//(case: not root)
switch(type){
case SIMPLE:
//(case: simple log message)
toPassOn = head.handle(toPass);
break;
case START_TRACK:
//(case: begin a new track)
toPassOn = head.signalStartTrack(toPass);
break;
case END_TRACK:
//case: end a track)
toPassOn = head.signalEndTrack(newDepth, timestamp);
break;
case SHUTDOWN:
//case: end a track)
toPassOn = head.signalShutdown();
break;
default:
throw new IllegalStateException("MessageType was non-exhaustive: " + type);
}
} else {
//(case: is root)
toPassOn = new ArrayList();
switch(type){
case SIMPLE:
toPassOn = append(toPassOn, toPass);
break;
case START_TRACK: break;
case END_TRACK: break;
case SHUTDOWN: break;
default: throw new IllegalStateException("MessageType was non-exhaustive: " + type);
}
}
//--Propagate Children
Iterator iter = children();
while(iter.hasNext()){ //for each child...
RecordHandlerTree child = iter.next();
//(auxilliary records)
for(Record r : toPassOn){ //for each record...
child.process(r, MessageType.SIMPLE, newDepth, timestamp);
}
//(special record)
switch(type){
case START_TRACK:
case END_TRACK:
case SHUTDOWN:
child.process(toPass, type, newDepth, timestamp);
break;
case SIMPLE: break;
default: throw new IllegalStateException("MessageType was non-exhaustive: " + type);
}
}
}
private StringBuilder toStringHelper(StringBuilder b, int depth){
for(int i=0; i 1){
Arrays.sort(channels, (a, b) -> {
if (a == FORCE) {
return -1;
} else if (b == FORCE) {
return 1;
} else if (a instanceof Flag && !(b instanceof Flag)) {
return -1;
} else if (b instanceof Flag && !(a instanceof Flag)) {
return 1;
} else {
return a.toString().compareTo(b.toString());
}
});
}
}
/**
* Returns whether this log message wants to be forced to be printed
* @return true if the FORCE flag is set on this message
*/
public boolean force(){ sort(); return this.channels.length > 0 && this.channels[0] == FORCE; }
/**
* Returns the channels for this record, in sorted order (special channels first, then alphabetical)
* @return A sorted list of channels
*/
public Object[] channels(){ sort(); return this.channels; }
@Override
public String toString() {
return "Record [content=" + content + ", depth=" + depth
+ ", channels=" + Arrays.toString(channels()) + ", thread=" + thread + ", timesstamp=" + timesstamp + "]";
}
}
/**
* Default output handler which actually prints things to the real System.out
*/
public static class ConsoleHandler extends OutputHandler {
PrintStream stream;
private ConsoleHandler(PrintStream stream){
this.stream = stream;
}
/**
* Print a string to the console, without the trailing newline
* @param channels The channel this line is being printed to;
* not relevant for this handler.
* @param line The string to be printed.
*/
@Override
public void print(Object[] channels, String line) {
stream.print(line); stream.flush();
}
@Override public boolean supportsAnsi() { return true; }
public static ConsoleHandler out(){ return new ConsoleHandler(realSysOut); }
public static ConsoleHandler err(){ return new ConsoleHandler(realSysErr); }
}
/**
* Handler which prints to a specified file
* TODO: make constructors for other ways of describing files (File, for example!)
*/
public static class FileHandler extends OutputHandler {
private PrintWriter printWriter;
public FileHandler(String filename) {
try {
printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), "utf-8")));
} catch (IOException e) {
Redwood.log(Flag.ERROR, e);
}
}
/** {@inheritDoc} */
@Override
public void print(Object[] channels, String line) {
printWriter.write(line == null ? "null" : line);
printWriter.flush();
}
}
/**
* A utility class for Redwood intended for static import
* (import static edu.stanford.nlp.util.logging.Redwood.Util.*;),
* providing a wrapper for Redwood functions and adding utility shortcuts
*/
@SuppressWarnings("UnusedDeclaration")
public static class Util {
private Util() {} // static methods
private static Object[] revConcat(Object[] B, Object... A) {
Object[] C = new Object[A.length+B.length];
System.arraycopy(A, 0, C, 0, A.length);
System.arraycopy(B, 0, C, A.length, B.length);
return C;
}
public static final Flag ERR = Flag.ERROR;
public static final Flag WARN = Flag.WARN;
public static final Flag DBG = Flag.DEBUG;
public static final Flag FORCE = Flag.FORCE;
public static final Flag STDOUT = Flag.STDOUT;
public static final Flag STDERR = Flag.STDERR;
public static void prettyLog(Object obj){ PrettyLogger.log(obj); }
public static void prettyLog(String description, Object obj){ PrettyLogger.log(description, obj); }
public static void log(Object...objs){ Redwood.log(objs); }
public static void logf(String format, Object... args){ Redwood.logf(format, args); }
public static void warn(Object...objs){ Redwood.log(revConcat(objs, WARN)); }
public static void debug(Object...objs){ Redwood.log(revConcat(objs, DBG)); }
public static void err(Object...objs){ Redwood.log(revConcat(objs, ERR, FORCE)); }
public static void fatal(Object...objs){ Redwood.log(revConcat(objs, ERR, FORCE)); System.exit(1); }
public static void println(Object o){ System.out.println(o); }
/** Exits with a given status code */
public static void exit(int exitCode){ Redwood.stop(); System.exit(exitCode); }
/** Exits with status code 0, stopping Redwood first */
public static void exit(){ exit(0); }
/** Create a RuntimeException with arguments */
public static RuntimeException fail(Object msg){
if(msg instanceof String){
return new RuntimeException((String) msg);
} else if(msg instanceof RuntimeException){
return (RuntimeException) msg;
} else if(msg instanceof Throwable){
return new RuntimeException((Throwable) msg);
} else {
throw new RuntimeException(msg.toString());
}
}
/** Create a new RuntimeException with no arguments */
public static RuntimeException fail(){ return new RuntimeException(); }
public static void startTrack(Object...objs){ Redwood.startTrack(objs); }
public static void forceTrack(String title){ Redwood.startTrack(FORCE, title); }
public static void endTrack(String check){ Redwood.endTrack(check); }
public static void endTrack(){ Redwood.endTrack(); }
public static void endTrackIfOpen(String check) {
if (!Redwood.titleStack.empty() && Redwood.titleStack.peek().equals(check)) { Redwood.endTrack(check); }
}
public static void endTracksUntil(String check) {
while (!Redwood.titleStack.empty() && !Redwood.titleStack.peek().equals(check)) { Redwood.endTrack(Redwood.titleStack.peek()); }
}
public static void endTracksTo(String check) { endTracksUntil(check); endTrack(check); }
public static void startThreads(String title){ Redwood.startThreads(title); }
public static void finishThread(){ Redwood.finishThread(); }
public static void endThreads(String check){ Redwood.endThreads(check); }
public static RedwoodChannels channels(Object... channels) { return new RedwoodChannels(channels); }
/**
* Wrap a collection of threads (Runnables) to be logged by Redwood.
* Each thread will be logged as a continuous chunk; concurrent threads will be queued
* and logged after the "main" thread has finished.
* This means that every Runnable passed to this method will run as a chunk, though in possibly
* random order.
*
* The handlers set up will operate on the output as if it were not concurrent -- timing will be preserved
* but repeated records will be collapsed as per the order the logs are actually output, rather than based
* on timestamp.
* @param title A title for the group of threads being run
* @param runnables The Runnables representing the tasks being run, without the Redwood overhead
* @return A new collection of Runnables with the Redwood overhead taken care of
*/
public static Iterable thread(final String title, Iterable runnables){
//--Preparation
//(variables)
final AtomicBoolean haveStarted = new AtomicBoolean(false);
final ReentrantLock metaInfoLock = new ReentrantLock();
final AtomicInteger numPending = new AtomicInteger(0);
final Iterator iter = runnables.iterator();
//--Create Runnables
return new IterableIterator(new Iterator() {
@Override
public boolean hasNext() {
synchronized (iter) {
return iter.hasNext();
}
}
@Override
public synchronized Runnable next() {
final Runnable runnable;
synchronized (iter) {
runnable = iter.next();
}
// (don't flood the queu)
while (numPending.get() > 100) {
try { Thread.sleep(100); }
catch (InterruptedException e) { }
}
numPending.incrementAndGet();
// (add the job)
Runnable toReturn = new Runnable(){
public void run(){
boolean threadFinished = false;
try{
//(signal start of threads)
metaInfoLock.lock();
if(!haveStarted.getAndSet(true)){
startThreads(title); //<--this must be a blocking operation
}
metaInfoLock.unlock();
//(run runnable)
try{
runnable.run();
} catch (Exception e){
e.printStackTrace();
System.exit(1);
} catch (AssertionError e) {
e.printStackTrace();
System.exit(1);
}
//(signal end of thread)
finishThread();
threadFinished = true;
//(signal end of threads)
int numStillPending = numPending.decrementAndGet();
synchronized (iter) {
if (numStillPending <= 0 && !iter.hasNext()) {
endThreads(title);
}
}
} catch(Throwable t){
t.printStackTrace();
if (!threadFinished) { finishThread(); }
}
}
};
return toReturn;
}
@Override
public void remove() {
synchronized (iter) {
iter.remove();
}
}
});
}
public static Iterable thread(Iterable runnables){ return thread("", runnables); }
/**
* Thread a collection of runnables, and run them via a java Executor.
* This is a utility function; the Redwood-specific changes happen in the
* thread() method.
* @param title A title for the group of threads being run
* @param runnables The Runnables representing the tasks being run, without the Redwood overhead --
* particularly, these should NOT have been passed to thread() yet.
* @param numThreads The number of threads to run on
*/
public static void threadAndRun(String title, Iterable runnables, int numThreads){
// (short circuit if single thread)
if (numThreads <= 1 || isThreaded || (runnables instanceof Collection && ((Collection) runnables).size() <= 1)) {
startTrack( "Threads (" + title + ")" );
for (Runnable toRun : runnables) { toRun.run(); }
endTrack( "Threads (" + title + ")" );
return;
}
//(create executor)
ExecutorService exec = Executors.newFixedThreadPool(numThreads);
//(add threads)
for(Runnable toRun : thread(title,runnables)){
exec.submit(toRun);
}
//(await finish)
exec.shutdown();
try {
exec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
}
public static void threadAndRun(String title, Iterable runnables){
threadAndRun(title,runnables,Runtime.getRuntime().availableProcessors());
}
public static void threadAndRun(Iterable runnables, int numThreads){
threadAndRun(""+numThreads, runnables, numThreads);
}
public static void threadAndRun(Iterable runnables){
threadAndRun(runnables, Execution.threads);
}
/**
* Print (to console) a margin with the channels of a given log message.
* Note that this does not affect File printing.
* @param width The width of the margin to print (must be >2)
*/
public static void printChannels(int width){
for(LogRecordHandler handler : handlers){
if(handler instanceof OutputHandler){
((OutputHandler) handler).leftMargin = width;
}
}
}
public static final Style BOLD = Style.BOLD;
public static final Style DIM = Style.DIM;
public static final Style ITALIC = Style.ITALIC;
public static final Style UNDERLINE = Style.UNDERLINE;
public static final Style BLINK = Style.BLINK;
public static final Style CROSS_OUT = Style.CROSS_OUT;
public static final Color BLACK = Color.BLACK;
public static final Color RED = Color.RED;
public static final Color GREEN = Color.GREEN;
public static final Color YELLOW = Color.YELLOW;
public static final Color BLUE = Color.BLUE;
public static final Color MAGENTA = Color.MAGENTA;
public static final Color CYAN = Color.CYAN;
public static final Color WHITE = Color.WHITE;
}
/**
* Represents a collection of channels. This lets you decouple selecting
* channels from logging messages, similar to traditional logging systems.
* {@link RedwoodChannels} have log and logf methods. Unlike Redwood.log and
* Redwood.logf, these do not take channel names since those are specified
* inside {@link RedwoodChannels}.
*
* Required if you want to use logf with a channel. This follows the
* Builder Pattern so Redwood.channels("chanA", "chanB").log("message") is equivalent to
* Redwood.channels("chanA").channels("chanB").log("message")
*/
public static class RedwoodChannels {
private final Object[] channelNames;
public RedwoodChannels(Object... channelNames) {
this.channelNames = channelNames;
}
/**
* Creates a new RedwoodChannels object, concatenating the channels from
* this RedwoodChannels with some additional channels.
* @param moreChannelNames The channel names to also include
* @return A RedwoodChannels representing the current and new channels.
*/
public RedwoodChannels channels(Object... moreChannelNames) {
//(copy array)
Object[] result = new Object[channelNames.length + moreChannelNames.length];
System.arraycopy(channelNames, 0, result, 0, channelNames.length);
System.arraycopy(moreChannelNames, 0, result, channelNames.length, moreChannelNames.length);
//(create channels)
return new RedwoodChannels(result);
}
/**
* Log a message to the channels specified in this RedwoodChannels object.
* @param obj The object to log
*/
public void log(Object... obj) {
Object[] newArgs = new Object[channelNames.length+obj.length];
System.arraycopy(channelNames,0,newArgs,0,channelNames.length);
System.arraycopy(obj,0,newArgs,channelNames.length,obj.length);
Redwood.log(newArgs);
}
/**
* Log a printf-style formatted message to the channels specified in this RedwoodChannels object.
* @param format The format string for the printf function
* @param args The arguments to the printf function
*/
public void logf(String format, Object... args) {
log(new Formatter().format(format, args));
}
/**
* PrettyLog an object using these channels. A default description will be created
* based on the type of obj.
*/
public void prettyLog(Object obj) {
PrettyLogger.log(this, obj);
}
/**
* PrettyLog an object with a description using these channels.
*/
public void prettyLog(String description, Object obj) {
PrettyLogger.log(this, description, obj);
}
public void warn(Object...objs){ log(Util.revConcat(objs, WARN)); }
public void debug(Object...objs){ log(Util.revConcat(objs, DBG)); }
public void err(Object...objs){ log(Util.revConcat(objs, ERR, FORCE)); }
public void fatal(Object...objs){ log(Util.revConcat(objs, ERR, FORCE)); System.exit(1); }
}
/**
* Standard channels; enum for the sake of efficiency
*/
protected static enum Flag {
ERROR,
WARN,
DEBUG,
STDOUT,
STDERR,
FORCE
}
/**
* Various informal tests of Redwood functionality
* @param args Unused
*
*/
// TODO(gabor) update this with the new RedwoodConfiguration
public static void main(String[] args){
Redwood.log(Redwood.DBG, "hello world!");
Redwood.hideChannelsEverywhere(Redwood.DBG);
Redwood.log(Redwood.DBG, "hello debug!");
System.exit(1);
// -- STRESS TEST THREADS --
LinkedList tasks = new LinkedList();
for(int i=0; i<1000; i++){
final int fI = i;
tasks.add(() -> {
startTrack("Runnable " + fI);
log(Thread.currentThread().getId());
log("message " + fI + ".1");
log("message " + fI + ".2");
log("message " + fI + ".3");
log(FORCE,"message " + fI + ".4");
log("message " + fI + ".5");
forceTrack("Runnable " + fI + ".1");
endTrack("Runnable " + fI + ".1");
forceTrack("Runnable " + fI + ".2");
log("a message");
endTrack("Runnable " + fI + ".2");
forceTrack("Runnable " + fI + ".3");
log("a message");
log(FORCE,"A forced message");
endTrack("Runnable " + fI + ".3");
endTrack("Runnable " + fI);
});
}
startTrack("Wrapper");
for(int i=0; i<100; i++){
Util.threadAndRun(tasks, 100);
}
endTrack("Wrapper");
System.exit(1);
forceTrack("Track 1");
log("tag", ERR, "hello world");
startTrack("Hidden");
startTrack("Subhidden");
endTrack("Subhidden");
endTrack("Hidden");
startTrack(FORCE, "Shown");
startTrack(FORCE,"Subshown");
endTrack("Subshown");
endTrack("Shown");
log("^shown should have appeared above");
startTrack("Track 1.1");
log(WARN, "some", "something in 1.1");
log("some",ERR,"something in 1.1");
log(FORCE,"some",WARN,"something in 1.1");
log(WARN,FORCE,"some","something in 1.1");
logf("format string %s then int %d", "hello", 7);
endTrack("Track 1.1");
startTrack();
log("In an anonymous track");
endTrack();
endTrack("Track 1");
log("outside of a track");
log("these","channels","should","be","in",DBG,"alphabetical","order", "a log item with lots of channels");
log("these","channels","should","be","in",DBG,"alphabetical","order", "a log item\nthat spans\nmultiple\nlines");
log(DBG,"a last log item");
log(ERR,null);
//--Repeated Records
// RedwoodConfiguration.current().collapseExact().apply();
//(simple case)
forceTrack("Strict Equality");
for(int i=0; i<100; i++){ log("this is a message"); }
endTrack("Strict Equality");
//(in-track change)
forceTrack("Change");
for(int i=0; i<10; i++){ log("this is a message"); }
for(int i=0; i<10; i++){ log("this is a another message"); }
for(int i=0; i<10; i++){ log("this is a third message"); }
for(int i=0; i<5; i++){ log("this is a fourth message"); }
log(FORCE,"this is a fourth message");
for(int i=0; i<5; i++){ log("this is a fourth message"); }
log("^middle 'fourth message' was forced");
endTrack("Change");
//(suppress tracks)
forceTrack("Repeated Tracks");
for(int i=0; i<100; i++){ startTrack("Track type 1"); log("a message"); endTrack("Track type 1"); }
for(int i=0; i<100; i++){ startTrack("Track type 2"); log("a message"); endTrack("Track type 2"); }
for(int i=0; i<100; i++){ startTrack("Track type 3"); log("a message"); endTrack("Track type 3"); }
startTrack("Track type 3"); startTrack("nested"); log(FORCE,"this should show up"); endTrack("nested"); endTrack("Track type 3");
for(int i=0; i<5; i++){ startTrack("Track type 3"); log(FORCE,"this should show up"); endTrack("Track type 3"); }
log(WARN,"The log message 'this should show up' should show up 6 (5+1) times above");
endTrack("Repeated Tracks");
//(tracks with invisible things)
// Redwood.hideOnlyChannels(DBG);
forceTrack("Hidden Subtracks");
for(int i=0; i<100; i++){
startTrack("Only has debug messages");
log(DBG,"You shouldn't see me");
endTrack("Only has debug messages");
}
log("You shouldn't see any other messages or 'skipped tracks' here");
endTrack("Hidden Subtracks");
//(fuzzy repeats)
RedwoodConfiguration.standard().apply();
// RedwoodConfiguration.current().collapseApproximate().apply();
forceTrack("Fuzzy Equality");
for(int i=0; i<100; i++){ log("iter " + i + " ended with value " + (-34587292534.0+Math.sqrt(i)*3000000000.0)); }
endTrack("Fuzzy Equality");
forceTrack("Fuzzy Equality (timing)");
for(int i=0; i<100; i++){
log("iter " + i + " ended with value " + (-34587292534.0+Math.sqrt(i)*3000000000.0));
try {
Thread.sleep(50);
} catch (InterruptedException e) { }
}
endTrack("Fuzzy Equality (timing)");
//--Util Helper
Util.log("hello world");
Util.log(DBG, "hello world");
Util.debug("hello world");
Util.debug("atag", "hello world");
//--Show Name at Track Finish
Redwood.getHandler(ConsoleHandler.class).minLineCountForTrackNameReminder = 5;
startTrack("Long Track");
for(int i=0; i<10; i++){ log(FORCE,"contents of long track"); }
endTrack("Long TracK");
startTrack("Long Track");
startTrack("But really this is the long one");
try {
Thread.sleep(3000);
} catch (InterruptedException e) { }
for(int i=0; i<10; i++){ log(FORCE,"contents of long track"); }
endTrack("But really this is the long one");
endTrack("Long TracK");
Redwood.getHandler(ConsoleHandler.class).minLineCountForTrackNameReminder = 50;
//--Multithreading
ExecutorService exec = Executors.newFixedThreadPool(10);
startThreads("name");
for(int i=0; i<50; i++){
final int theI = i;
exec.execute(() -> {
startTrack("Thread " + theI + " (" + Thread.currentThread().getId() + ")");
for(int time=0; time<5; time++){
log("tick " + time + " from " + theI + " (" + Thread.currentThread().getId() + ")");
try {
Thread.sleep(50);
} catch (Exception e) {}
}
endTrack("Thread " + theI + " (" + Thread.currentThread().getId() + ")");
finishThread();
});
}
exec.shutdown();
try {
exec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
} catch (InterruptedException e) {}
endThreads("name");
//--System Streams
Redwood.captureSystemStreams(true, true);
System.out.println("Hello World");
System.err.println("This is an error!");
//--Neat Exit
// RedwoodConfiguration.standard().collapseExact().apply();
//(on close)
for(int i=0; i<100; i++){
// startTrack();
log("stuff!");
// endTrack();
}
Util.exit(0);
//(on exception)
System.out.println("I'm going to exception soon (on purpose)");
RedwoodConfiguration.current().neatExit().apply();
startTrack("I should close");
log(FORCE,"so I'm nonempty...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
throw new IllegalArgumentException();
}
}