edu.stanford.nlp.util.logging.RedwoodConfiguration Maven / Gradle / Ivy
Show all versions of stanford-parser Show documentation
package edu.stanford.nlp.util.logging;
import java.io.File;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;
import edu.stanford.nlp.util.Generics;
/**
* A class which encapsulates configuration settings for Redwood.
* The class operates on the builder model; that is, you can chain method
* calls.
* @author Gabor Angeli (angeli at cs.stanford)
*/
public class RedwoodConfiguration {
/**
* A list of tasks to run when the configuration is applied
*/
private LinkedList tasks = new LinkedList();
private OutputHandler outputHandler = Redwood.ConsoleHandler.out();
private File defaultFile = new File("/dev/null");
private int channelWidth = 0;
/**
* Private constructor to prevent use of "new RedwoodConfiguration()"
*/
protected RedwoodConfiguration(){}
/**
* Apply this configuration to Redwood
*/
public void apply(){
for(Runnable task : tasks){ task.run(); }
}
/**
* Capture a system stream
* @param stream The stream to capture; one of System.out or System.err
* @return this
*/
public RedwoodConfiguration capture(final OutputStream stream) {
if (stream == System.out) {
tasks.add(() -> Redwood.captureSystemStreams(true, Redwood.realSysErr == System.err));
} else if (stream == System.err) {
tasks.add(() -> Redwood.captureSystemStreams(Redwood.realSysOut == System.out, true));
} else {
throw new IllegalArgumentException("Must capture one of stderr or stdout");
}
return this;
}
public RedwoodConfiguration restore(final OutputStream stream) {
if (stream == System.out) {
tasks.add(() -> Redwood.captureSystemStreams(false, Redwood.realSysErr == System.err));
} else if (stream == System.err) {
tasks.add(() -> Redwood.captureSystemStreams(Redwood.realSysOut == System.out, false));
} else {
throw new IllegalArgumentException("Must capture one of stderr or stdout");
}
return this;
}
/**
* Determine where, in the end, console output should go.
* The default is stdout.
* @param method An output, one of: stdout, stderr, or java.util.logging
* @return this
*/
public RedwoodConfiguration output(final String method) {
if (method.equalsIgnoreCase("stdout") || method.equalsIgnoreCase("out")){
edu.stanford.nlp.util.logging.JavaUtilLoggingAdaptor.adapt();
this.outputHandler = Redwood.ConsoleHandler.out();
} else if (method.equalsIgnoreCase("stderr") || method.equalsIgnoreCase("err")) {
edu.stanford.nlp.util.logging.JavaUtilLoggingAdaptor.adapt();
this.outputHandler = Redwood.ConsoleHandler.err();
} else if (method.equalsIgnoreCase("java.util.logging")){
edu.stanford.nlp.util.logging.JavaUtilLoggingAdaptor.adapt();
this.outputHandler = RedirectOutputHandler.fromJavaUtilLogging(Logger.getLogger("``error``"));
} else {
throw new IllegalArgumentException("Unknown value for log.method");
}
return this;
}
/**
* Set the width of the channels (or 0 to not show channels)
* @param width The left margin in which to show channels
* @return this
*/
public RedwoodConfiguration channelWidth(final int width) {
tasks.add(() -> RedwoodConfiguration.this.channelWidth = width);
return this;
}
/**
* Clear any custom configurations to Redwood
* @return this
*/
public RedwoodConfiguration clear(){
this.tasks = new LinkedList();
this.tasks.add(() -> {
Redwood.clearHandlers();
Redwood.restoreSystemStreams();
});
this.outputHandler = Redwood.ConsoleHandler.out();
return this;
}
public static interface Thunk {
public void apply(RedwoodConfiguration config, Redwood.RecordHandlerTree root);
}
@SuppressWarnings("UnusedDeclaration")
public static class Handlers {
//
// Leaf destinations
//
/**
* Output to a file. This is a leaf node.
* Consider using "defaultFile" instead.
* @param path The file to write to
*/
public static Thunk file(final String path) {
return new Thunk() {
@Override
public void apply(final RedwoodConfiguration config, Redwood.RecordHandlerTree root) {
root.addChild(new Redwood.FileHandler(path){{ this.leftMargin = config.channelWidth; }});
}
};
}
/**
* Output to a file. This is a leaf node.
* Consider using "defaultFile" instead.
* @param path The file to write to
*/
public static Thunk file(File path) { return file(path.getPath()); }
/**
* Output to a file. This is a leaf node.
* Consider using this instead of specifying a custom path.
*/
public static final Thunk defaultFile = new Thunk() {
@Override
public void apply(final RedwoodConfiguration config, Redwood.RecordHandlerTree root) {
root.addChild(new Redwood.FileHandler(config.defaultFile.getPath()){{ this.leftMargin = config.channelWidth; }});
}
};
/**
* Output to a standard output. This is a leaf node.
* Consider using "output" instead, unless you really
* want to log only to stdout now and forever in the future.
*/
public static final Thunk stdout = (config, root) -> {
Redwood.ConsoleHandler handler = Redwood.ConsoleHandler.out();
handler.leftMargin = config.channelWidth;
root.addChild(handler);
};
/**
* Output to a standard error. This is a leaf node.
* Consider using "output" instead, unless you really
* want to log only to stderr now and forever in the future.
*/
public static final Thunk stderr = (config, root) -> {
Redwood.ConsoleHandler handler = Redwood.ConsoleHandler.err();
handler.leftMargin = config.channelWidth;
root.addChild(handler);
};
/**
* Output to the default location specified by the output() method.
* Consider using this rather than stderr or stdout.
*/
public static final Thunk output = (config, root) -> {
config.outputHandler.leftMargin = config.channelWidth;
root.addChild(config.outputHandler);
};
//
// Filters
//
/**
* Hide the debug channel only.
*/
public static final LogRecordHandler hideDebug = new VisibilityHandler() {{
alsoHide(Redwood.DBG);
}};
/**
* Hide the error channel only.
*/
public static final LogRecordHandler hideError = new VisibilityHandler() {{
alsoHide(Redwood.ERR);
}};
/**
* Hide the warning channel only.
*/
public static final LogRecordHandler hideWarn = new VisibilityHandler() {{
alsoHide(Redwood.WARN);
}};
/**
* Show only errors (e.g., to send them to an error file)
*/
public static final LogRecordHandler showOnlyError = new VisibilityHandler() {{
hideAll();
alsoShow(Redwood.ERR);
}};
/**
* Hide these channels, in addition to anything already hidden by upstream handlers.
*/
public static LogRecordHandler hideChannels(final Object... channelsToHide) {
return new VisibilityHandler() {{
for (Object channel : channelsToHide) {
alsoHide(channel);
}
}};
}
/**
* Show only these channels, as far as downstream handlers are concerned.
*/
public static LogRecordHandler showOnlyChannels(final Object... channelsToShow) {
return new VisibilityHandler() {{
hideAll();
for (Object channel : channelsToShow) {
alsoShow(channel);
}
}};
}
/**
* Rename a channel to be something else
*/
public static LogRecordHandler reroute(final Object src, final Object dst) {
return new RerouteChannel(src, dst);
}
/**
* Collapse records in a heuristic way to make reading easier. This is particularly relevant to branches which
* go to a physical console, or a file which you'd like to keep small.
*/
public static final LogRecordHandler collapseApproximate = new RepeatedRecordHandler(RepeatedRecordHandler.APPROXIMATE);
/**
* Collapse records which are duplicates into a single message, followed by a message detailing how many times
* it was repeated.
*/
public static final LogRecordHandler collapseExact = new RepeatedRecordHandler(RepeatedRecordHandler.EXACT);
//
// Combinators
//
/**
* Send any incoming messages multiple ways.
* For example, you may want to send the same output to console and a file.
* @param destinations The destinations for log messages coming into this node.
*/
public static Thunk branch(final Thunk... destinations) {
return new Thunk() {
@Override
public void apply(RedwoodConfiguration config, Redwood.RecordHandlerTree root) {
for (Thunk destination : destinations) {
destination.apply(config, root);
}
}
};
}
/**
* Apply each of the handlers to incoming log messages, in sequence.
* @param handlers The handlers to apply
* @param destination The final destination of the messages, after processing
*/
public static Thunk chain(final LogRecordHandler[] handlers, final Thunk destination) {
return new Thunk() {
private Redwood.RecordHandlerTree buildChain(RedwoodConfiguration config, LogRecordHandler[] handlers, int i) {
Redwood.RecordHandlerTree rtn = new Redwood.RecordHandlerTree(handlers[i]);
if (i < handlers.length - 1) {
rtn.addChildTree( buildChain(config, handlers, i + 1) );
} else {
destination.apply(config, rtn);
}
return rtn;
}
@Override
public void apply(RedwoodConfiguration config, Redwood.RecordHandlerTree root) {
if (handlers.length == 0) {
destination.apply(config, root);
} else {
root.addChildTree(buildChain(config, handlers, 0));
}
}
};
}
/** @see #chain(LogRecordHandler[], RedwoodConfiguration.Thunk) */
public static Thunk chain(LogRecordHandler handler1, Thunk destination) { return chain(new LogRecordHandler[]{ handler1 }, destination); }
/** @see #chain(LogRecordHandler[], RedwoodConfiguration.Thunk) */
public static Thunk chain(LogRecordHandler handler1, LogRecordHandler handler2, Thunk destination) { return chain(new LogRecordHandler[]{ handler1, handler2 }, destination); }
/** @see #chain(LogRecordHandler[], RedwoodConfiguration.Thunk) */
public static Thunk chain(LogRecordHandler handler1, LogRecordHandler handler2, LogRecordHandler handler3, Thunk destination) { return chain(new LogRecordHandler[]{ handler1, handler2, handler3 }, destination); }
/** @see #chain(LogRecordHandler[], RedwoodConfiguration.Thunk) */
public static Thunk chain(LogRecordHandler handler1, LogRecordHandler handler2, LogRecordHandler handler3, LogRecordHandler handler4, Thunk destination) { return chain(new LogRecordHandler[]{ handler1, handler2, handler3, handler4 }, destination); }
/** @see #chain(LogRecordHandler[], RedwoodConfiguration.Thunk) */
public static Thunk chain(LogRecordHandler handler1, LogRecordHandler handler2, LogRecordHandler handler3, LogRecordHandler handler4, LogRecordHandler handler5, Thunk destination) { return chain(new LogRecordHandler[]{ handler1, handler2, handler3, handler4, handler5 }, destination); }
/**
* A NOOP, as the name implies. Useful for appending to the end of lists to make commas match.
*/
public static Thunk noop = (config, root) -> {
};
}
/**
*
*
* For example:
*
* handlers(branch(
* chain( hideDebug, collapseApproximate, branch( output, file("stderr.log") ),
* chain( showOnlyError, file("err.log") ).
* chain( showOnlyChannels("results", "evaluate"), file("results.log") ),
* chain( file("redwood.log") ),
* noop))
*
*
* @param paths A number of paths to add.
* @return this
*/
public RedwoodConfiguration handlers(Thunk... paths) {
for (final Thunk thunk : paths) {
tasks.add(() -> thunk.apply(RedwoodConfiguration.this, Redwood.rootHandler()));
}
return this;
}
/**
* Close tracks when the JVM shuts down.
* @return this
*/
public RedwoodConfiguration neatExit(){
tasks.add(() -> Runtime.getRuntime().addShutdownHook(new Thread(){
@Override public void run(){ Redwood.stop(); }
}));
return this;
}
/**
* An empty Redwood configuration.
* Note that without a Console Handler, Redwood will not print anything
* @return An empty Redwood Configuration object.
*/
public static RedwoodConfiguration empty(){
return new RedwoodConfiguration().clear();
}
/**
* The default Redwood configuration, which prints to the console.
* This is the usual starting point for new configurations.
* @return A basic Redwood Configuration.
*/
public static RedwoodConfiguration standard(){
return new RedwoodConfiguration().clear().handlers(
Handlers.chain(Handlers.hideChannels(), Handlers.stderr)
);
}
/**
* The current Redwood configuration; this is used to make incremental changes
* to an existing custom configuration.
* @return The current Redwood configuration.
*/
public static RedwoodConfiguration current(){
return new RedwoodConfiguration();
}
/**
* Helper for parsing properties
* @param p The properties object
* @param key The key to retrieve
* @param defaultValue The default value if the key does not exist
* @param used The set of keys we have seen
* @return The value of the property at the key
*/
private static String get(Properties p, String key, String defaultValue, Set used){
Object cand = p.get(key);
String rtn;
if (cand == null) {
rtn = p.getProperty(key, defaultValue);
} else {
rtn = cand.toString();
}
used.add(key);
return rtn;
}
/**
* Configure Redwood (from scratch) based on a Properties file.
* Currently recognized properties are:
*
* - log.captureStreams = {true,false}: Capture stdout and stderr and route them through Redwood
* - log.captureStdout = {true,false}: Capture stdout and route it through Redwood
* - log.captureStderr = {true,false}: Capture stdout and route it through Redwood
* - log.channels.width = {number}: Show the channels being logged to, at this width (default: 0; recommended: 20)
* - log.channels.debug = {true,false}: Show the debugging channel
* - log.file = By default, write to this file.
*
- log.neatExit = {true,false}: Clean up logs on exception or regular system exit
* - log.output = {stderr,stdout,java.util.logging}: Output messages to either stderr or stdout by default.
*
* @param props The properties to use in configuration
* @return A new Redwood Configuration based on the passed properties, ignoring any existing custom configuration
*/
public static RedwoodConfiguration parse(Properties props){
RedwoodConfiguration config = new RedwoodConfiguration().clear();
Set used = Generics.newHashSet();
//--Capture Streams
if(get(props,"log.captureStreams","false",used).equalsIgnoreCase("true")){
config = config.capture(System.out).capture(System.err);
}
if(get(props,"log.captureStdout","false",used).equalsIgnoreCase("true")){
config = config.capture(System.out);
}
if(get(props,"log.captureStderr","false",used).equalsIgnoreCase("true")){
config = config.capture(System.err);
}
//--Collapse
String collapse = get(props, "log.collapse", "none", used);
List chain = new LinkedList();
if (collapse.equalsIgnoreCase("exact")) {
chain.add(new RepeatedRecordHandler(RepeatedRecordHandler.EXACT));
} else if (collapse.equalsIgnoreCase("approximate")) {
chain.add(new RepeatedRecordHandler(RepeatedRecordHandler.APPROXIMATE));
} else if (!collapse.equalsIgnoreCase("none")) {
throw new IllegalArgumentException("Unknown collapse mode (Redwood): " + collapse);
}
//--Channels.Debug
boolean debug = Boolean.parseBoolean(get(props, "log.channels.debug", "true", used));
if (!debug) {
chain.add(Handlers.hideDebug);
}
//--Channels.Width
config.channelWidth( Integer.parseInt(get(props, "log.channels.width", "0", used)) );
//--Neat exit
if(get(props,"log.neatExit","false",used).equalsIgnoreCase("true")){
config = config.neatExit();
}
//--File
String outputFile = get(props, "log.file", null, used);
if (outputFile != null) {
config.defaultFile = new File(outputFile);
config = config.handlers(Handlers.defaultFile);
}
//--Console
config = config.output(get(props, "log.output", "stdout", used));
//--Console
config = config.handlers(Handlers.chain(chain.toArray(new LogRecordHandler[chain.size()]), Handlers.output));
//--Error Check
for(Object propAsObj : props.keySet()) {
String prop = propAsObj.toString();
if(prop.startsWith("log.") && !used.contains(prop)){
throw new IllegalArgumentException("Could not find Redwood log property: " + prop);
}
}
//--Return
return config;
}
/**
* Parses a properties file and applies it immediately to Redwood
* @param props The properties to apply
*/
public static void apply(Properties props){
parse(props).apply();
}
/*
public static void main(String[] args) {
RedwoodConfiguration.empty().neatExit().capture(System.out).capture(System.err)
.channelWidth(20)
.handlers(
Handlers.chain(Handlers.hideDebug, Handlers.output),
Handlers.file("/tmp/redwood.log"))
.apply();
Redwood.log("foo");
Redwood.log(Redwood.DBG, "debug");
System.out.println("Bar");
System.err.println("Baz");
}
*/
}