de.unkrig.commons.util.logging.SimpleLogging Maven / Gradle / Ivy
/*
* de.unkrig.commons - A general-purpose Java class library
*
* Copyright (c) 2011, Arno Unkrig
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.unkrig.commons.util.logging;
import java.io.File;
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.unkrig.commons.lang.ExceptionUtil;
import de.unkrig.commons.nullanalysis.Nullable;
import de.unkrig.commons.text.expression.EvaluationException;
import de.unkrig.commons.text.expression.ExpressionEvaluator;
import de.unkrig.commons.text.expression.Parser;
import de.unkrig.commons.text.parser.ParseException;
import de.unkrig.commons.util.logging.formatter.PrintfFormatter;
import de.unkrig.commons.util.logging.formatter.SelectiveFormatter;
import de.unkrig.commons.util.logging.handler.ProxyHandler;
import de.unkrig.commons.util.logging.handler.StderrHandler;
import de.unkrig.commons.util.logging.handler.StdoutHandler;
/**
* A utility class that simplifies the usage of Java™'s {@code java.util.logging} facility.
*
* Typically, you simply call
*
* {@code SimpleLogging.init();}
*
* as soon as possible when your Java program starts (perhaps from a static initializer of your main class).
*
*
* Later, at any possible point of time, you'd optionally call one of the following:
*
*
*
* {@link #setQuiet()}
* Suppress {@code LOGGER.info()} messages
*
*
* {@link #setNoWarn()}
* Suppress {@code LOGGER.info()} and {@code LOGGER.warning()} messages
*
*
* {@link #setNormal()}
* (Resets SimpleLogging to ist default behavior.)
*
*
* {@link #setVerbose()}
* Also print {@code LOGGER.config()} messages to STDOUT
*
*
* {@link #setDebug()}
* Also print {@code LOGGER.fine()} messages to STDERR*
*
*
* {@link #setDebug()}
{@link #setDebug()}
* Also print {@code LOGGER.finer()} messages to STDERR*
*
*
* {@link #setDebug()}
{@link #setDebug()}
{@link #setDebug()}
* Also print {@code LOGGER.finest()} messages to STDERR*
*
*
*
* * These messages are printed with source class name, source method name, exception (if any) and stack
* trace.
*
*
* Alternatively, you can call {@link #setLevel(Level)} with a respective level parameter.
*
*
* Alternatively, you may want to call {@link #configureLoggers(String)} to configure log levels, handlers and
* formats for individual loggers. For example,
*
*
* {@code SimpleLogging.configureLoggers("FINEST:com.acme:FileHandler(\"foo.txt\"):java.util.logging.XMLFormatter");}
*
*
* activates full debugging for logger "com.acme" and its children (e.g. "com.acme.foo"), and writes to the file
* "foo.txt" in XML format.
*
*/
public final
class SimpleLogging {
private
SimpleLogging() {}
/**
* Logs the messages of all levels below {@link Level#CONFIG CONFIG} (FINE, FINER, FINEST, ...) to STDERR.
*/
private static final Handler DEBUG_HANDLER;
/**
* Logs the messages of levels {@link Level#CONFIG CONFIG} (inclusive) through {@link Level#WARNING WARNING}
* (exclusive) to STDOUT or a custom {@link Handler} (see {@link #setOut(Handler)}.
*/
private static final ProxyHandler OUT_HANDLER;
/**
* Logs the messages of levels {@link Level#WARNING WARNING} and higher to STDERR.
*/
private static final Handler STDERR_HANDLER;
// Default formatters for the handlers.
private static final Formatter DEFAULT_DEBUG_FORMATTER = new PrintfFormatter("%10$27s::%7$-15s %8$s%9$s%n");
private static final Formatter DEFAULT_OUT_FORMATTER = SelectiveFormatter.loggerLevelGreaterThan(
Level.FINE,
PrintfFormatter.MESSAGE_AND_EXCEPTION,
PrintfFormatter.MESSAGE_AND_STACK_TRACE
);
private static final Formatter DEFAULT_STDERR_FORMATTER = SimpleLogging.DEFAULT_OUT_FORMATTER;
static {
DEBUG_HANDLER = new StderrHandler();
SimpleLogging.DEBUG_HANDLER.setFilter(LogUtil.LESS_THAN_CONFIG);
SimpleLogging.DEBUG_HANDLER.setLevel(Level.ALL);
SimpleLogging.DEBUG_HANDLER.setFormatter(SimpleLogging.DEFAULT_DEBUG_FORMATTER);
LogUtil.ROOT_LOGGER.addHandler(SimpleLogging.DEBUG_HANDLER);
OUT_HANDLER = new ProxyHandler(new StdoutHandler());
SimpleLogging.OUT_HANDLER.setFilter(LogUtil.LESS_THAN_WARNING);
SimpleLogging.OUT_HANDLER.setLevel(Level.CONFIG);
SimpleLogging.OUT_HANDLER.setFormatter(SimpleLogging.DEFAULT_OUT_FORMATTER);
LogUtil.ROOT_LOGGER.addHandler(SimpleLogging.OUT_HANDLER);
STDERR_HANDLER = new StderrHandler();
SimpleLogging.STDERR_HANDLER.setLevel(Level.WARNING);
SimpleLogging.STDERR_HANDLER.setFormatter(SimpleLogging.DEFAULT_STDERR_FORMATTER);
LogUtil.ROOT_LOGGER.addHandler(SimpleLogging.STDERR_HANDLER);
}
/**
* Sets up the default configuration.
*
* -
* Messages with levels >= {@link Level#WARNING WARNING} are printed to STDERR.
*
* -
* Messages with levels {@link Level#INFO INFO} are printed to STDOUT.
*
* -
* Messages with levels <= {@link Level#CONFIG CONFIG} are not printed.
*
*
*/
public static void
init() {
// Everything is already done in static initializers.
;
}
/**
* Configures the logging of a command-line utility as usual.
*
*
* Typical command line option
* level
* Levels logged to STDERR
* Levels logged to STDOUT
*
* -nowarn SEVERE SEVERE -
* -quiet WARNING SEVERE, WARNING -
* (none) INFO SEVERE, WARNING INFO
* -verbose CONFIG SEVERE, WARNING INFO, CONFIG
*
* -debug
* FINEST
* SEVERE, WARNING
FINE, FINER, FINEST*
* INFO, CONFIG
*
*
* *: FINE, FINER and FINEST log records are printed with class, method, source and line number
*/
public static synchronized void
setLevel(Level level) {
LogUtil.ROOT_LOGGER.setLevel(level);
}
/**
* @return The currently configured level for the root logger
* @see #setLevel(Level)
*/
public static Level
getLevel() {
return LogUtil.ROOT_LOGGER.getLevel();
}
/**
* Sets the formatter for all three handlers (debug, out and stderr).
*
* @param spec The expression to parse and to evaluate
* @see Parser The expression syntax
*/
public static void
setFormatter(String spec) throws ParseException, EvaluationException {
Formatter formatter = SimpleLogging.FORMATTER_INSTANTIATOR.evaluateTo(spec, Formatter.class);
if (formatter == null) {
SimpleLogging.DEBUG_HANDLER.setFormatter(SimpleLogging.DEFAULT_DEBUG_FORMATTER);
SimpleLogging.OUT_HANDLER.setFormatter(SimpleLogging.DEFAULT_OUT_FORMATTER);
SimpleLogging.STDERR_HANDLER.setFormatter(SimpleLogging.DEFAULT_STDERR_FORMATTER);
} else {
SimpleLogging.setFormatter(formatter);
}
}
/**
* Sets the formatter for all three handlers (debug, out and stderr).
*/
public static void
setFormatter(Formatter formatter) {
SimpleLogging.DEBUG_HANDLER.setFormatter(formatter);
SimpleLogging.OUT_HANDLER.setFormatter(formatter);
SimpleLogging.STDERR_HANDLER.setFormatter(formatter);
}
/**
* Shorthand for {@code setLevel(Level.SEVERE + 1)}: Messages of levels {@code INFO}, {@code WARNING} and {@code
* SEVERE} are suppressed.
*/
public static void
setNoError() {
SimpleLogging.setLevel(LogUtil.SEVERE_PLUS_1);
}
/**
* Shorthand for {@code setLevel(Level.WARNING + 1)}: Messages of levels {@code INFO} and {@code WARNING} are
* suppressed.
*/
public static void
setNoWarn() {
SimpleLogging.setLevel(LogUtil.WARNING_PLUS_1);
}
/**
* Shorthand for {@code setLevel(Level.INFO + 1)}: Messages of level {@link Level#INFO}, i.e. 'normal output' are
* suppressed.
*/
public static void
setQuiet() {
SimpleLogging.setLevel(LogUtil.INFO_PLUS_1);
}
/**
* Shorthand for {@code setLevel(Level.INFO)}: Messages of level {@link Level#INFO INFO}, i.e. 'normal output' and
* above ({@link Level#WARNING WARNING} and {@link Level#SEVERE SEVERE}) are logged.
*/
public static void
setNormal() {
SimpleLogging.setLevel(Level.INFO);
}
/**
* Shorthand for {@link #setLevel(Level) setLevel}{@code (Level.CONFIG)}: Messages of level {@link Level#CONFIG
* CONFIG}, i.e. "verbose output" are logged.
*/
public static void
setVerbose() {
SimpleLogging.setLevel(Level.CONFIG);
}
/**
* Shorthand for {@code setLevel(FINE)}. All messages down to level {@link Level#FINE FINE} are logged.
*
* Calling this method multiply lowers the level to {@link Level#FINER} and then {@link Level#FINEST}.
*/
public static void
setDebug() {
Level l = SimpleLogging.getLevel();
SimpleLogging.setLevel(
l == Level.FINER ? Level.FINEST :
l == Level.FINE ? Level.FINER :
Level.FINE
);
}
/**
* Installs a {@link Handler} which writes messages of levels {@link Level#INFO INFO} (inclusive) through {@link
* Level#WARNING WARNING} (exclusive) to STDOUT.
*/
public static void
setStdout() {
SimpleLogging.setOut(new StdoutHandler());
}
/**
* Sets a {@link Handler} which writes messages of levels {@link Level#INFO INFO} (inclusive) through {@link
* Level#WARNING WARNING} (exclusive) to the given {@link File}.
*/
public static void
setOut(File value) throws IOException {
SimpleLogging.setOut(new FileHandler(value.getPath()));
}
/**
* Sets the given {@link Handler} for messages of levels {@link Level#INFO INFO} (inclusive) through {@link
* Level#WARNING WARNING} (exclusive).
*
* Clients may want to use this method to redirect their "normal" output (not the errors, warnings and debug
* output) elsewhere, e.g. into an "output file".
*
* @param handler {@code null} to reset to the 'normal' behavior (print to STDOUT)
*/
public static synchronized void
setOut(@Nullable Handler handler) {
// Close the old delegate.
SimpleLogging.OUT_HANDLER.close();
// Set the new delegate.
SimpleLogging.OUT_HANDLER.setDelegate(handler == null ? new StdoutHandler() : handler);
// Configure the new delegate.
SimpleLogging.OUT_HANDLER.setFilter(LogUtil.LESS_THAN_WARNING);
SimpleLogging.OUT_HANDLER.setLevel(Level.INFO);
SimpleLogging.OUT_HANDLER.setFormatter(SimpleLogging.DEFAULT_OUT_FORMATTER);
}
static {
for (Handler handler : LogUtil.ROOT_LOGGER.getHandlers()) {
if (handler instanceof ConsoleHandler) {
handler.close();
LogUtil.ROOT_LOGGER.removeHandler(handler);
}
}
SimpleLogging.setNormal();
}
private static final ExpressionEvaluator
HANDLER_INSTANTIATOR = new ExpressionEvaluator().addOnDemandImports(new String[] {
"java.util.logging",
"de.unkrig.commons.util.logging.handler",
});
private static final ExpressionEvaluator
FORMATTER_INSTANTIATOR = new ExpressionEvaluator().addOnDemandImports(new String[] {
"java.util.logging",
"de.unkrig.commons.util.logging.formatter",
});
// SUPPRESS CHECKSTYLE LineLength:8
/**
* Sets the level of the named loggers, adds the given handler on them and sets the given
* formatter on the handler.
*
* The spec is parsed as follows:
*
*
* spec := [ level ] [ ':' [ logger-names ] [ ':' [ handler ] [ ':' [ formatter ] [ ':' use-parent-handlers ] ] ] ]
* logger-names := logger-name [ ',' logger-name ]...
*
*
* The level component determines the log level of the handler, or, iff no
* handler is given, the level of the loggers.
* It must be parsable by {@link Level#parse(String)}, i.e. it must be a decimal number, or one of {@code
* SEVERE}, {@code WARNING}, {@code INFO}, {@code CONFIG}, {@code FINE}, {@code FINER}, {@code FINEST} or {@code
* ALL}.
*
*
* The handler and formatter components denote {@link Parser expressions}, with the
* automatically imported packages "{@code java.util.logging}", "{@code de.unkrig.commons.util.logging.handler}"
* and "{@code de.unkrig.commons.util.logging.formatter}". Notice that neither expression may contain colons.
*
*
* Example spec:
*
*
* FINE:de.unkrig:ConsoleHandler:FormatFormatter("%5$tF %5$tT.%5$tL %10$-20s %3$2d %8$s%9$s%n")
*
*
* If any of the components of the spec is missing or empty, a reasonable default value is assumed:
*
*
* - level
* -
* Logger: Level inherited from parent logger
*
* Handler: Handler's default log level, typically {@code ALL}
*
*
* - loggers
* -
* The root logger
*
*
* - handler
* -
* No handler; only the parent loggers' handlers will be called (iff {@code user-parent-handlers} is {@code
* true})
*
*
* - formatter
* -
* {@link PrintfFormatter#MESSAGE_AND_EXCEPTION MESSAGE_AND_EXCEPTION}
*
*
* - use-parent-handlers
* -
* {@code true}
*
*
*
* It is recommended that command line tools call this method from their {@code main(String[])} method, on each
* occurrence of a {@code "-log} spec{@code "} command line option.
*
*
* @see Parser The expression syntax of the handler and the formatter components
*/
public static void
configureLoggers(String spec) {
String[] args = new String[5];
{
String[] sa = spec.split(":", 5);
for (int i = 0; i < args.length && i < sa.length; i++) {
String s = sa[i].trim();
if (!"".equals(s)) args[i] = s;
}
}
// Argument 1 == level
Level level;
if (args[0] != null) {
level = Level.parse(args[0]);
} else {
level = null;
}
// Argument 2 == loggers
Logger[] loggers;
if (args[1] != null) {
String loggerNames = args[1];
String[] tmp = loggerNames.split(",");
loggers = new Logger[tmp.length];
for (int i = 0; i < tmp.length; i++) {
loggers[i] = Logger.getLogger(tmp[i]);
}
} else {
loggers = new Logger[] { LogUtil.ROOT_LOGGER };
}
// Argument 3 == handler
Handler handler;
if (args[2] != null) {
String handlerSpec = args[2];
try {
handler = SimpleLogging.HANDLER_INSTANTIATOR.evaluateTo(handlerSpec, Handler.class);
} catch (Exception e) {
throw ExceptionUtil.wrap(handlerSpec + ": " + e.getMessage(), e, RuntimeException.class);
}
} else {
handler = null;
}
// Argument 4 == formatter
Formatter formatter;
if (args[3] != null) {
String formatterSpec = args[3];
try {
formatter = SimpleLogging.FORMATTER_INSTANTIATOR.evaluateTo(formatterSpec, Formatter.class);
} catch (Exception e) {
throw ExceptionUtil.wrap(
"Evaluating formatter spec \"" + formatterSpec + "\": " + e.getMessage(),
e,
RuntimeException.class
);
}
} else {
formatter = PrintfFormatter.MESSAGE_AND_EXCEPTION;
}
// Argument 5 == useParentHandlers
boolean useParentHandlers = true;
if (args[4] != null) {
useParentHandlers = Boolean.parseBoolean(args[4]);
}
// Configure the handler.
if (handler != null) {
if (level != null) handler.setLevel(level);
handler.setFormatter(formatter);
}
// Configure the loggers.
for (Logger logger : loggers) {
if (handler != null) logger.addHandler(handler);
if (level != null && !logger.isLoggable(level)) logger.setLevel(level);
logger.setUseParentHandlers(useParentHandlers);
}
}
}