/*
* 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.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Properties;
import java.util.logging.Handler;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import de.lmu.ifi.dbs.elki.logging.Logging.Level;
/**
* Facility for configuration of logging.
*
* @author Erich Schubert
* @since 0.2
*/
public final class LoggingConfiguration {
/**
* General debug flag.
*/
public static boolean DEBUG = false;
/**
* Configuration file name
*/
private static final String LOGGING_PROPERTIES_FILE = "logging.properties";
/**
* Top level ELKI package (for setting 'verbose')
*/
private static final String TOPLEVEL_PACKAGE = "de.lmu.ifi.dbs.elki";
/**
* Top level logger.
*/
private static final Logger LOGGER_GLOBAL_TOP = Logger.getLogger("");
/**
* Logger for ELKI top level package.
*/
private static final Logger LOGGER_ELKI_TOP = Logger.getLogger(TOPLEVEL_PACKAGE);
/**
* Logger for ELKI timing.
*/
private static final Logger LOGGER_TIME_TOP = Logger.getLogger(TOPLEVEL_PACKAGE + ".workflow.AlgorithmStep");
/**
* Configuration base
*/
private static final String confbase = LoggingConfiguration.class.getPackage().getName();
/**
* Static instance of the configuration
*/
protected static LoggingConfiguration config = new LoggingConfiguration(confbase, System.getProperty("java.util.logging.config.file", LOGGING_PROPERTIES_FILE));
/**
* Configure Java Logging API: {@link java.util.logging.LogManager}
*/
private LoggingConfiguration(final String pkg, final String name) {
privateReconfigureLogging(pkg, name);
}
/**
* Reconfigure logging.
*
* @param pkg Package name the configuration file comes from
* @param name File name.
*/
public static void reconfigureLogging(final String pkg, final String name) {
config.privateReconfigureLogging(pkg, name);
}
/**
* Reconfigure logging.
*
* @param pkg Package name the configuration file comes from
* @param name File name.
*/
private void privateReconfigureLogging(String pkg, final String name) {
LogManager logManager = LogManager.getLogManager();
Logger logger = Logger.getLogger(LoggingConfiguration.class.getName());
// allow null as package name.
if(pkg == null) {
pkg = "";
}
// Load logging configuration from current directory
String cfgfile = name;
if(new File(name).exists()) {
cfgfile = name;
}
else {
// Fall back to full path / resources.
cfgfile = pkg.replace('.', File.separatorChar) + File.separatorChar + name;
}
try {
InputStream cfgdata = openSystemFile(cfgfile);
logManager.readConfiguration(cfgdata);
// also load as properties for us, to get debug flag.
InputStream cfgdata2 = openSystemFile(cfgfile);
Properties cfgprop = new Properties();
cfgprop.load(cfgdata2);
DEBUG = Boolean.parseBoolean(cfgprop.getProperty("debug"));
logger.info("Logging configuration read.");
}
catch(FileNotFoundException e) {
logger.log(Level.SEVERE, "Could not find logging configuration file: " + cfgfile, e);
}
catch(Exception e) {
logger.log(Level.SEVERE, "Failed to configure logging from file: " + cfgfile, e);
}
}
/**
* Private copy from FileUtil, to avoid cross-dependencies. Try to open a
* file, first trying the file system, then falling back to the classpath.
*
* @param filename File name in system notation
* @return Input stream
* @throws FileNotFoundException When no file was found.
*/
private static InputStream openSystemFile(String filename) throws FileNotFoundException {
try {
return new FileInputStream(filename);
}
catch(FileNotFoundException e) {
// try with classloader
String resname = File.separatorChar != '/' ? filename.replace(File.separatorChar, '/') : filename;
ClassLoader cl = LoggingConfiguration.class.getClassLoader();
InputStream result = cl.getResourceAsStream(resname);
if(result != null) {
return result;
}
// Sometimes, URLClassLoader does not work right. Try harder:
URL u = cl.getResource(resname);
if(u == null) {
throw e;
}
try {
URLConnection conn = u.openConnection();
conn.setUseCaches(false);
result = conn.getInputStream();
if(result != null) {
return result;
}
}
catch(IOException x) {
throw e; // Throw original error instead.
}
throw e;
}
}
/**
* Assert that logging was configured.
*/
public static void assertConfigured() {
// nothing happening here, just to ensure static construction was run.
}
/**
* Reconfigure logging to enable 'verbose' logging at the top level.
*
* @param verbose verbosity level.
*/
public static void setVerbose(java.util.logging.Level verbose) {
if(verbose.intValue() <= Level.VERBOSE.intValue()) {
// decrease to VERBOSE if it was higher, otherwise further to
// VERYVERBOSE
if(LOGGER_GLOBAL_TOP.getLevel() == null || LOGGER_GLOBAL_TOP.getLevel().intValue() > verbose.intValue()) {
LOGGER_GLOBAL_TOP.setLevel(verbose);
}
if(LOGGER_ELKI_TOP.getLevel() == null || LOGGER_ELKI_TOP.getLevel().intValue() > verbose.intValue()) {
LOGGER_ELKI_TOP.setLevel(verbose);
}
}
else {
// re-increase to given level if it was verbose or "very verbose".
if(LOGGER_GLOBAL_TOP.getLevel() != null && (//
Level.VERBOSE.equals(LOGGER_GLOBAL_TOP.getLevel()) || //
Level.VERYVERBOSE.equals(LOGGER_GLOBAL_TOP.getLevel()) //
)) {
LOGGER_GLOBAL_TOP.setLevel(verbose);
}
if(LOGGER_ELKI_TOP.getLevel() != null && (//
Level.VERBOSE.equals(LOGGER_ELKI_TOP.getLevel()) || //
Level.VERYVERBOSE.equals(LOGGER_ELKI_TOP.getLevel()) //
)) {
LOGGER_ELKI_TOP.setLevel(verbose);
}
}
}
/**
* Enable runtime performance logging.
*/
public static void setStatistics() {
// decrease to INFO if it was higher
if(LOGGER_GLOBAL_TOP.getLevel() == null || LOGGER_GLOBAL_TOP.getLevel().intValue() > Level.STATISTICS.intValue()) {
LOGGER_GLOBAL_TOP.setLevel(Level.STATISTICS);
}
if(LOGGER_ELKI_TOP.getLevel() == null || LOGGER_ELKI_TOP.getLevel().intValue() > Level.STATISTICS.intValue()) {
LOGGER_ELKI_TOP.setLevel(Level.STATISTICS);
}
if(LOGGER_TIME_TOP.getLevel() == null || LOGGER_TIME_TOP.getLevel().intValue() > Level.STATISTICS.intValue()) {
LOGGER_TIME_TOP.setLevel(Level.STATISTICS);
}
}
/**
* Add a handler to the root logger.
*
* @param handler Handler
*/
public static void addHandler(Handler handler) {
LogManager.getLogManager().getLogger("").addHandler(handler);
}
/**
* Replace the default log handler with the given log handler.
*
* This will remove all {@link CLISmartHandler} found on the root logger. It
* will leave any other handlers in place.
*
* @param handler Logging handler.
*/
public static void replaceDefaultHandler(Handler handler) {
Logger rootlogger = LogManager.getLogManager().getLogger("");
for(Handler h : rootlogger.getHandlers()) {
if(h instanceof CLISmartHandler) {
rootlogger.removeHandler(h);
}
}
addHandler(handler);
}
/**
* Set the logging level for a particular package/class.
*
* @param pkg Package
* @param level Level name
* @throws IllegalArgumentException thrown when logger or level was not found
*/
public static void setLevelFor(String pkg, String level) throws IllegalArgumentException {
Logger logr = Logger.getLogger(pkg);
if(logr == null) {
throw new IllegalArgumentException("Logger not found.");
}
// Can also throw an IllegalArgumentException
java.util.logging.Level lev = Level.parse(level);
logr.setLevel(lev);
}
/**
* Set the default level.
*
* @param level level
*/
public static void setDefaultLevel(java.util.logging.Level level) {
Logger.getLogger(TOPLEVEL_PACKAGE).setLevel(level);
}
}