package com.twitter.common.logging;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.twitter.common.args.Arg;
import com.twitter.common.args.CmdLine;
/**
* A configuration class for the root java.util.logging Logger.
*
* Defines flags to control the behavior behavior of the root logger similarly to Google's glog
* library (see http://code.google.com/p/google-glog ).
*/
public class RootLogConfig {
/**
* An enum reflecting log {@link Level} constants.
*/
public enum LogLevel {
FINEST(Level.FINEST),
FINER(Level.FINER),
FINE(Level.FINE),
CONFIG(Level.CONFIG),
INFO(Level.INFO),
WARNING(Level.WARNING),
SEVERE(Level.SEVERE);
private final Level level;
private LogLevel(Level level) {
this.level = level;
}
private Level getLevel() {
return level;
}
private int intValue() {
return level.intValue();
}
}
@CmdLine(name = "logtostderr", help = "Log messages to stderr instead of logfiles.")
private static Arg<Boolean> LOGTOSTDERR = Arg.create(false);
@CmdLine(name = "alsologtostderr",
help = "Log messages to stderr, in addition to log files. Ignored when --logtostderr")
private static Arg<Boolean> ALSOLOGTOSTDERR = Arg.create(false);
@CmdLine(name = "vlog",
help = "The value is one of the constants in java.util.logging.Level. "
+ "Shows all messages with level equal or higher "
+ "than the value of this flag.")
private static Arg<LogLevel> VLOG = Arg.create(LogLevel.INFO);
@CmdLine(name = "vmodule",
help = "Per-class verbose level. The argument has to contain a comma-separated list "
+ "of <class_name>=<log_level>. <class_name> is the full-qualified name of a "
+ "class, <log_level> is one of the constants in java.util.logging.Level. "
+ "<log_level> overrides any value given by --vlog.")
private static Arg<Map<Class<?>, LogLevel>> VMODULE =
Arg.<Map<Class<?>, LogLevel>>create(new HashMap<Class<?>, LogLevel>());
// TODO(franco): change this flag's default to true, then remove after enough forewarning.
@CmdLine(name = "use_glog_formatter", help = "True to use the glog formatter exclusively.")
private static Arg<Boolean> USE_GLOG_FORMATTER = Arg.create(false);
/**
* A builder-pattern class used to perform the configuration programmatically
* (i.e. not through flags).
* Example:
* <code>
* RootLogConfig.builder().logToStderr(true).apply();
* </code>
*/
public static class Configuration {
private boolean logToStderr = false;
private boolean alsoLogToStderr = false;
private boolean useGLogFormatter = false;
private LogLevel vlog = null;
private Map<Class<?>, LogLevel> vmodule = null;
private String rootLoggerName = "";
private Configuration() {}
/**
* Only log messages to stderr, instead of log files. Overrides alsologtostderr.
* Default: false.
*
* @param flag True to enable, false to disable.
* @return this Configuration object.
*/
public Configuration logToStderr(boolean flag) {
this.logToStderr = flag;
return this;
}
/**
* Also log messages to stderr, in addition to log files.
* Overridden by logtostderr.
* Default: false.
*
* @param flag True to enable, false to disable.
* @return this Configuration object.
*/
public Configuration alsoLogToStderr(boolean flag) {
this.alsoLogToStderr = flag;
return this;
}
/**
* Format log messages in one-line with a header, similar to google-glog.
* Default: false.
*
* @param flag True to enable, false to disable.
* @return this Configuration object.
*/
public Configuration useGLogFormatter(boolean flag) {
this.useGLogFormatter = flag;
return this;
}
/**
* Output log messages at least at the given verbosity level.
* Overridden by vmodule.
* Default: INFO
*
* @param level LogLevel enumerator for the minimum log message verbosity level that is output.
* @return this Configuration object.
*/
public Configuration vlog(LogLevel level) {
Preconditions.checkNotNull(level);
this.vlog = level;
return this;
}
/**
* Output log messages for a given set of classes at the associated verbosity levels.
* Overrides vlog.
* Default: no classes are treated specially.
*
* @param pairs Map of classes and correspoding log levels.
* @return this Configuration object.
*/
public Configuration vmodule(Map<Class<?>, LogLevel> pairs) {
Preconditions.checkNotNull(pairs);
this.vmodule = pairs;
return this;
}
/**
* Applies this configuration to the root log.
*/
public void apply() {
RootLogConfig.configure(this);
}
// Intercepts the root logger, for testing purposes only.
@VisibleForTesting
Configuration rootLoggerName(String name) {
Preconditions.checkNotNull(name);
Preconditions.checkArgument(!name.isEmpty());
this.rootLoggerName = name;
return this;
}
}
/**
* Creates a new Configuration builder object.
* @return The newly built Configuration.
*/
public static Configuration builder() {
return new Configuration();
}
/**
* Configures the root log properties using flags.
* This is the entry point used by AbstractApplication via LogModule.
*/
public static void configureFromFlags() {
builder()
.logToStderr(LOGTOSTDERR.get())
.alsoLogToStderr(ALSOLOGTOSTDERR.get())
.useGLogFormatter(USE_GLOG_FORMATTER.get())
.vlog(VLOG.get())
.vmodule(VMODULE.get())
.apply();
}
private static void configure(Configuration configuration) {
// Edit the properties of the root logger.
Logger rootLogger = Logger.getLogger(configuration.rootLoggerName);
if (configuration.logToStderr) {
setLoggerToStderr(rootLogger);
} else if (configuration.alsoLogToStderr) {
setLoggerToAlsoStderr(rootLogger);
}
if (configuration.useGLogFormatter) {
setGLogFormatter(rootLogger);
}
if (configuration.vlog != null) {
setVlog(rootLogger, configuration.vlog);
}
if (configuration.vmodule != null) {
setVmodules(configuration.vmodule);
}
}
private static void setLoggerToStderr(Logger logger) {
LogManager.getLogManager().reset();
setConsoleHandler(logger, true);
}
private static void setLoggerToAlsoStderr(Logger logger) {
setConsoleHandler(logger, false);
}
private static void setConsoleHandler(Logger logger, boolean removeOtherHandlers) {
Handler consoleHandler = null;
for (Handler h : logger.getHandlers()) {
if (h instanceof ConsoleHandler) {
consoleHandler = h;
} else if (removeOtherHandlers) {
logger.removeHandler(h);
}
}
if (consoleHandler == null) {
consoleHandler = new ConsoleHandler();
logger.addHandler(new ConsoleHandler());
}
consoleHandler.setLevel(Level.ALL);
consoleHandler.setFilter(null);
}
private static void setGLogFormatter(Logger logger) {
for (Handler h : logger.getHandlers()) {
h.setFormatter(new LogFormatter());
}
}
private static void setVmodules(Map<Class<?>, LogLevel> vmodules) {
for (Map.Entry<Class<?>, LogLevel> entry : vmodules.entrySet()) {
String className = entry.getKey().getName();
Logger logger = Logger.getLogger(className);
setVlog(logger, entry.getValue());
}
}
private static void setVlog(Logger logger, LogLevel logLevel) {
final Level newLevel = logLevel.getLevel();
logger.setLevel(newLevel);
do {
for (Handler handler : logger.getHandlers()) {
Level handlerLevel = handler.getLevel();
if (newLevel.intValue() < handlerLevel.intValue()) {
handler.setLevel(newLevel);
}
}
} while (logger.getUseParentHandlers() && (logger = logger.getParent()) != null);
}
// Utility class.
private RootLogConfig() {
}
}