/*
* This file is part of ELKI:
* Environment for Developing KDD-Applications Supported by Index-Structures
*
* Copyright (C) 2017
* ELKI Development Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.lmu.ifi.dbs.elki.logging;
import java.util.HashMap;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import de.lmu.ifi.dbs.elki.logging.progress.AbstractProgress;
import de.lmu.ifi.dbs.elki.logging.progress.FiniteProgress;
import de.lmu.ifi.dbs.elki.logging.progress.IndefiniteProgress;
import de.lmu.ifi.dbs.elki.logging.progress.Progress;
import de.lmu.ifi.dbs.elki.logging.progress.ProgressLogRecord;
import de.lmu.ifi.dbs.elki.logging.progress.StepProgress;
import de.lmu.ifi.dbs.elki.logging.statistics.Counter;
import de.lmu.ifi.dbs.elki.logging.statistics.Duration;
import de.lmu.ifi.dbs.elki.logging.statistics.MillisTimeDuration;
import de.lmu.ifi.dbs.elki.logging.statistics.Statistic;
import de.lmu.ifi.dbs.elki.logging.statistics.UnsynchronizedLongCounter;
/**
* This class is a wrapper around {@link java.util.logging.Logger} and
* {@link java.util.logging.LogManager} offering additional convenience
* functions.
*
* If a class keeps a static reference to the appropriate {@link Logging}
* object, performance penalty compared to standard logging should be minimal.
*
* However when using {@link java.util.logging.LogRecord} directly instead of
* {@link ELKILogRecord}, the use of the {@link #log(LogRecord)} method will
* result in incorrectly logged cause location. Therefore, use
* {@link ELKILogRecord}!
*
* @author Erich Schubert
* @since 0.2
*
* @apiviz.uses LoggingConfiguration
* @apiviz.uses ELKILogRecord oneway - - «create»
* @apiviz.uses Level
*/
public class Logging {
/**
* Ensure that logging is configured.
*/
static {
LoggingConfiguration.assertConfigured();
}
/**
* HashMap to keep track of loggers.
*/
private static HashMap<String, Logging> loggers = new HashMap<>();
/**
* Wrapped logger of this instance - not static!
*/
private final Logger logger;
/**
* Logging Level class.
*
* @author Erich Schubert
*/
public static class Level extends java.util.logging.Level {
/**
* Additional level for logging: statistics and timing information.
*
* Inbetween of "verbose" and "warning".
*/
public static final Level STATISTICS = new Level("STATISTICS", (INFO.intValue() + WARNING.intValue()) >> 1);
/**
* Alias for the "INFO" logging level: "verbose".
*/
public static final java.util.logging.Level VERBOSE = INFO;
/**
* Additional level for logging: additional verbose messages.
*
* Inbetween of "verbose" and "config", usually 750.
*/
public static final Level VERYVERBOSE = new Level("VERYVERBOSE", (INFO.intValue() + CONFIG.intValue()) >> 1);
/**
* Serial version.
*/
private static final long serialVersionUID = 1L;
/**
* Constructor.
*
* @param name Name
* @param value Value
*/
public Level(String name, int value) {
super(name, value);
}
/**
* Parse a logging level.
*
* @param levelName Name of level to parse.
* @return {@link java.util.logging.Level} level
*/
public static java.util.logging.Level parse(String levelName) {
// While this is a pass-through to the parent class,
// it ensures our own level have been added.
// Otherwise, levels such as "STATISTICS" might not work!
return java.util.logging.Level.parse(levelName);
}
}
/**
* Constructor, wrapping a logger.
*
* @param logger Logger to wrap.
*/
public Logging(final Logger logger) {
this.logger = logger;
}
/**
* Retrieve logging utility for a particular class.
*
* @param c Class to retrieve logging for
* @return Logger
*/
public static Logging getLogger(final Class<?> c) {
return getLogger(c.getName());
}
/**
* Retrieve logging utility for a particular class.
*
* @param name Class name
* @return Logger
*/
public synchronized static Logging getLogger(final String name) {
Logging logger = loggers.get(name);
if(logger == null) {
logger = new Logging(Logger.getLogger(name));
loggers.put(name, logger);
}
return logger;
}
/**
* Verify if logging is enabled at that particular level.
*
* @param lev Logging level
* @return status
*/
public boolean isLoggable(Level lev) {
return logger.isLoggable(lev);
}
/**
* Test whether to log 'statistics'.
*
* @return true if logging statistics
*/
public boolean isStatistics() {
return logger.isLoggable(Level.STATISTICS);
}
/**
* Test whether to log 'verbose' aka 'info'.
*
* @return true if verbose
*/
public boolean isVerbose() {
return logger.isLoggable(Level.VERBOSE);
}
/**
* Test whether to log 'info' aka 'verbose'.
*
* @return true if verbose
*/
public boolean isInfo() {
return logger.isLoggable(Level.INFO);
}
/**
* Test whether to log 'veryverbose'.
*
* @return true if extra verbose
*/
public boolean isVeryVerbose() {
return logger.isLoggable(Level.VERYVERBOSE);
}
/**
* Test whether to log 'debug' at 'FINE' level.
*
* This is the same as {@link #isDebuggingFine}
*
* @return true if debug logging enabled
*/
public boolean isDebugging() {
return logger.isLoggable(Level.FINE);
}
/**
* Test whether to log 'debug' at 'FINE' level
*
* This is the same as {@link #isDebugging}
*
* @return true if debug logging enabled
*/
public boolean isDebuggingFine() {
return logger.isLoggable(Level.FINE);
}
/**
* Test whether to log 'debug' at 'FINER' level
*
* @return true if debug logging enabled
*/
public boolean isDebuggingFiner() {
return logger.isLoggable(Level.FINER);
}
/**
* Test whether to log 'debug' at 'FINEST' level
*
* @return true if debug logging enabled
*/
public boolean isDebuggingFinest() {
return logger.isLoggable(Level.FINEST);
}
/**
* Log a log message at the given level.
*
* @param level Level to log at.
* @param message Message to log.
*/
public void log(java.util.logging.Level level, CharSequence message) {
LogRecord rec = new ELKILogRecord(level, message);
logger.log(rec);
}
/**
* Log a log message and exception at the given level.
*
* @param level Level to log at.
* @param message Message to log.
* @param e Exception
*/
public void log(java.util.logging.Level level, CharSequence message, Throwable e) {
LogRecord rec = new ELKILogRecord(level, message);
rec.setThrown(e);
logger.log(rec);
}
/**
* Log a given log record (should be a {@link ELKILogRecord})
*
* @param rec Log record to log.
*/
public void log(LogRecord rec) {
logger.log(rec);
}
/**
* Log a message at the 'severe' level.
*
* @param message Warning log message.
* @param e Exception
*/
public void error(CharSequence message, Throwable e) {
log(Level.SEVERE, message, e);
}
/**
* Log a message at the 'severe' level.
*
* @param message Warning log message.
*/
public void error(CharSequence message) {
log(Level.SEVERE, message);
}
/**
* Log a message at the 'warning' level.
*
* @param message Warning log message.
* @param e Exception
*/
public void warning(CharSequence message, Throwable e) {
log(Level.WARNING, message, e);
}
/**
* Log a message at the 'warning' level.
*
* @param message Warning log message.
*/
public void warning(CharSequence message) {
log(Level.WARNING, message);
}
/**
* Log a message at the 'STATISTICS' level.
*
* You should check isTime() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void statistics(CharSequence message, Throwable e) {
log(Level.STATISTICS, message, e);
}
/**
* Log a message at the 'STATISTICS' level.
*
* You should check isTime() before building the message.
*
* @param message Informational log message.
*/
public void statistics(CharSequence message) {
log(Level.STATISTICS, message);
}
/**
* Log a message at the 'info' ('verbose') level.
*
* You should check isVerbose() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void verbose(CharSequence message, Throwable e) {
log(Level.INFO, message, e);
}
/**
* Log a message at the 'info' ('verbose') level.
*
* You should check isVerbose() before building the message.
*
* @param message Informational log message.
*/
public void verbose(CharSequence message) {
log(Level.INFO, message);
}
/**
* Log a message at the 'info' ('verbose') level.
*
* You should check isVerbose() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void info(CharSequence message, Throwable e) {
log(Level.INFO, message, e);
}
/**
* Log a message at the 'info' ('verbose') level.
*
* You should check isVerbose() before building the message.
*
* @param message Informational log message.
*/
public void info(CharSequence message) {
log(Level.INFO, message);
}
/**
* Log a message at the 'veryverbose' level.
*
* You should check isVeryVerbose() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void veryverbose(CharSequence message, Throwable e) {
log(Level.VERYVERBOSE, message, e);
}
/**
* Log a message at the 'veryverbose' level.
*
* You should check isVeryVerbose() before building the message.
*
* @param message Informational log message.
*/
public void veryverbose(CharSequence message) {
log(Level.VERYVERBOSE, message);
}
/**
* Log a message at the 'fine' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void debug(CharSequence message, Throwable e) {
log(Level.FINE, message, e);
}
/**
* Log a message at the 'fine' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
*/
public void debug(CharSequence message) {
log(Level.FINE, message);
}
/**
* Log a message at the 'fine' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void debugFine(CharSequence message, Throwable e) {
log(Level.FINE, message, e);
}
/**
* Log a message at the 'fine' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
*/
public void debugFine(CharSequence message) {
log(Level.FINE, message);
}
/**
* Log a message at the 'fine' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void fine(CharSequence message, Throwable e) {
log(Level.FINE, message, e);
}
/**
* Log a message at the 'fine' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
*/
public void fine(CharSequence message) {
log(Level.FINE, message);
}
/**
* Log a message at the 'finer' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void debugFiner(CharSequence message, Throwable e) {
log(Level.FINER, message, e);
}
/**
* Log a message at the 'finer' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
*/
public void debugFiner(CharSequence message) {
log(Level.FINER, message);
}
/**
* Log a message at the 'finer' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void finer(CharSequence message, Throwable e) {
log(Level.FINER, message, e);
}
/**
* Log a message at the 'finer' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
*/
public void finer(CharSequence message) {
log(Level.FINER, message);
}
/**
* Log a message at the 'finest' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void debugFinest(CharSequence message, Throwable e) {
log(Level.FINEST, message, e);
}
/**
* Log a message at the 'finest' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
*/
public void debugFinest(CharSequence message) {
log(Level.FINEST, message);
}
/**
* Log a message at the 'finest' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
* @param e Exception
*/
public void finest(CharSequence message, Throwable e) {
log(Level.FINEST, message, e);
}
/**
* Log a message at the 'finest' debugging level.
*
* You should check isDebugging() before building the message.
*
* @param message Informational log message.
*/
public void finest(CharSequence message) {
log(Level.FINEST, message);
}
/**
* Log a message with exception at the 'severe' level.
*
* @param message Error log message.
* @param e Exception
*/
public void exception(CharSequence message, Throwable e) {
log(Level.SEVERE, message, e);
}
/**
* Log an exception at the 'severe' level.
*
* @param e Exception
*/
public void exception(Throwable e) {
final String msg = e.getMessage();
log(Level.SEVERE, msg != null ? msg : "An exception occurred.", e);
}
/**
* Log a Progress object.
*
* @param pgr Progress to log.
*/
public void progress(Progress pgr) {
logger.log(new ProgressLogRecord(Level.INFO, pgr));
}
/**
* Generate a new counter.
*
* @param key Key to use
* @return Counter.
*/
public Counter newCounter(String key) {
return new UnsynchronizedLongCounter(key);
}
/**
* Generate a new duration statistic.
*
* @param key Key to use
* @return Duration statistic.
*/
public Duration newDuration(String key) {
return new MillisTimeDuration(key);
}
/**
* Increment a progress (unless {@code null}).
*
* @param prog Progress to increment, may be {@code null}.
*/
public void incrementProcessed(AbstractProgress prog) {
if(prog != null) {
prog.incrementProcessed(this);
}
}
/**
* Increment a progress (unless {@code null}).
*
* @param prog Progress to complete, may be {@code null}.
*/
public void ensureCompleted(FiniteProgress prog) {
if(prog != null) {
prog.ensureCompleted(this);
}
}
/**
* Begin a new algorithm step (unless {@code null}).
*
* <b>Important:</b> Do not use this method when the parameter are not static.
* In these cases, check whether logging is enabled first, to avoid computing
* method parameters!
*
* @param prog Progress to increment, may be {@code null}.
* @param step Step number
* @param title Step title
*/
public void beginStep(StepProgress prog, int step, String title) {
if(prog != null) {
prog.beginStep(step, title, this);
}
}
/**
* Finish a progress (unless {@code null}).
*
* @param prog Progress to complete, may be {@code null}.
*/
public void setCompleted(StepProgress prog) {
if(prog != null) {
prog.setCompleted(this);
}
}
/**
* Finish a progress (unless {@code null}).
*
* @param prog Progress to complete, may be {@code null}.
*/
public void setCompleted(IndefiniteProgress prog) {
if(prog != null) {
prog.setCompleted(this);
}
}
/**
* Log a statistics object.
*
* @param stats Statistics object to report.
*/
public void statistics(Statistic stats) {
log(Level.STATISTICS, stats.getKey() + ": " + stats.formatValue());
}
@Override
public String toString() {
return "Logging(" + logger.getName() + ", " + logger.getLevel() + ")";
}
}