/* * RapidMiner * * Copyright (C) 2001-2008 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * 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 com.rapidminer.tools; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.Date; import com.rapidminer.NoBugError; import com.rapidminer.Process; import com.rapidminer.RapidMiner; import com.rapidminer.gui.RapidMinerGUI; import com.rapidminer.operator.Operator; import com.rapidminer.operator.ProcessRootOperator; import com.rapidminer.parameter.UndefinedParameterError; import com.rapidminer.tools.log.DeleteLogFormatFilter; import com.rapidminer.tools.log.DummyLogFormatFilter; import com.rapidminer.tools.log.FormattedFilterStream; import com.rapidminer.tools.log.StreamMultiplier; /** * <p>Utility class providing static methods for logging.<br> * Parameters read from the XML process configuration file:</p> * <ul> * <li>logfile (filename or "stdout" or "stderr")</li> * <li>logverbosity (possible values are in * {@link LogService#LOG_VERBOSITY_NAMES}</li> * </ul> * * <p>Beside the <b>local</b> log service associated with a concrete process and which will * be automatically initialized during the setup phase, one <b>global</b> log service exist * which is used for generic log messages not bound to the operators used in a process. * This global log service is usually initialized to log messages on system out (at least during the * basic initialization phase of RapidMiner). After the basic intialization phase, * the global messages will be presented in the message viewer (if the RapidMiner GUI * is used) or still printed to system out or in any other stream defined via the * method {@link #initGlobalLogging(OutputStream, int)}. Alternatively, one could also define an * environment variable named {@link RapidMiner#PROPERTY_RAPIDMINER_GLOBAL_LOG_FILE}.</p> * * <p>Usually, operators should only use the log verbosities MINIMUM for messages * with a low priority and STATUS for normal information messages. In rare cases, the * verbosity level NOTE could be used for operators stating some message more * important then STATUS (hence the user should see the message for the default * log verbosity level of INIT) but not as important then WARNING. The verbosity * levels WARNING, EXCEPTION, and ERROR should be used in error cases. All other * log verbosity levels should only be used by internal RapidMiner classes and not by * user written operators.</p> * * <p>We recommend to set the parameter for the log verbosity level to INIT for the * process design phase (eventually STATUS for debugging) and to the log verbosity * level WARNING in the production phase. This way it is ensured that not too many logging * messages are produced in the production phase.</p> * * <p>Log messages can be formatted by using the following macros:</p> * <ul> * <li>"$b" and "^b" start and end bold mode respectively</li> * <li>"$i" and "^i" start and end italic mode respectively</li> * <li>"$m" and "^m" start and end monospace mode respectively</li> * <li>"$n" and "^n" start and end note color mode respectively</li> * <li>"$w" and "^w" start and end warning color mode respectively</li> * <li>"$e" and "^e" start and end error color mode respectively</li> * </ul> * * @author Ingo Mierswa * @version $Id: LogService.java,v 1.12 2008/05/09 19:22:55 ingomierswa Exp $ */ public class LogService implements LoggingHandler { /** The prefix used to indicate the global logger. */ public static final String GLOBAL_PREFIX = "$gG^g"; // -------------------- Verbosity Level -------------------- /** Indicates an unknown verbosity level. */ public static final int UNKNOWN_LEVEL = -1; /** * Indicates the lowest log verbosity. Should only be used for very detailed * but not necessary logging. */ public static final int MINIMUM = 0; /** * Indicates log messages concerning in- and output. Should only be used by * the class Operator itself and not by its subclasses. */ public static final int IO = 1; /** The default log verbosity for all logging purposes of operators. */ public static final int STATUS = 2; /** * Only the most important logging messaged should use this log verbosity. * Currently used only by the LogService itself. */ public static final int INIT = 3; /** Use this log verbosity for logging of important notes, i.e. things less important than warnings * but important enough to see for all not interested in the detailed status messages. */ public static final int NOTE = 4; /** Use this log verbosity for logging of warnings. */ public static final int WARNING = 5; /** Use this log verbosity for logging of errors. */ public static final int ERROR = 6; /** * Use this log verbosity for logging of fatal errors which will stop * process running somewhere in the future. */ public static final int FATAL = 7; /** * Normally this log verbosity should not be used by operators. Messages * with this verbosity will always be displayed. */ public static final int MAXIMUM = 8; /** For switching off logging during testing. */ public static final int OFF = 9; public static final String LOG_VERBOSITY_NAMES[] = { "all", "io", "status", "init", "notes", "warning", "error", "fatal", "almost_none", "off" }; private static final String[] VERBOSITYLEVEL_START = { "", "", "", "", "$n$b[NOTE]^b ", "$w$b[Warning]^b ", "$e$b[Error]^b ", "$e$b[Fatal]^b ", "", "" }; private static final String[] VERBOSITYLEVEL_END = { "", "", "", "", "^n", "^w", "^e", "^e" , "", "" }; /** The global logging. */ private static LogService globalLogging = null; /** The PrintStream to write the messages to. */ private PrintStream logOut = System.out; /** The minimal verbosity level. Message below this level are ignored. */ private int minVerbosityLevel = INIT; /** The last printed message. */ private String lastMessage; /** Counts how often a message was repeated. */ private int equalMessageCount; private File logFile = null; private String prefix = GLOBAL_PREFIX; private Process process = null; // ------ methods for init ------- /** Creates a log service for this process. The properties * (possible log file and log verbosity) are taken from the * process parameters.*/ public LogService(Process process) throws UndefinedParameterError { this(process, UNKNOWN_LEVEL); } /** Creates a log service for this process. The properties * (possible log file and log verbosity) are taken from the * process parameters. If the given logVerbosity is a valid * verbosity level, then this level is used instead of the * level defined in the process. Otherwise, the given log * verbosity must have value UNKNOWN_LEVEL and the process * level will be used. */ public LogService(Process process, int logVerbosity) throws UndefinedParameterError { this.process = process; int verbosityLevel = logVerbosity; if (verbosityLevel == UNKNOWN_LEVEL) { verbosityLevel = process.getRootOperator().getParameterAsInt(ProcessRootOperator.PARAMETER_LOGVERBOSITY); } String fileName = process.getRootOperator().getParameterAsString(ProcessRootOperator.PARAMETER_LOGFILE); init(fileName, verbosityLevel, "$gP^g"); } /** Creates a log service with verbosity INIT logging on system out * (default global logging). */ private LogService() { init(System.out, INIT, false, GLOBAL_PREFIX); } /** Returns the global logging. If no logging was otherwise create, this * method creates the default standard out log service if no log file * was defined in the property {@link RapidMiner#PROPERTY_RAPIDMINER_GLOBAL_LOG_FILE}. * Alternatively, developers can invoke the method {@link #initGlobalLogging(OutputStream, int)}. */ public static synchronized LogService getGlobal() { if (globalLogging == null) { globalLogging = new LogService(); String globalLogFile = System.getProperty(RapidMiner.PROPERTY_RAPIDMINER_GLOBAL_LOG_FILE); String globalLogVerbosity = System.getProperty(RapidMiner.PROPERTY_RAPIDMINER_GLOBAL_LOG_VERBOSITY); int logVerbosity = getVerbosityLevel(globalLogVerbosity); if ((logVerbosity < UNKNOWN_LEVEL) || (logVerbosity >= LOG_VERBOSITY_NAMES.length)) { globalLogging.logError("Only numbers between " + MINIMUM + " and " + MAXIMUM + " or one of the log verbosity level names are allowed as value for " + RapidMiner.PROPERTY_RAPIDMINER_GLOBAL_LOG_VERBOSITY + ". Was: '" + globalLogVerbosity + "'. Using INIT instead..."); logVerbosity = INIT; } globalLogging.init(globalLogFile, logVerbosity, GLOBAL_PREFIX); } return globalLogging; } /** Initializes the global logging, i.e. the global log service not bound * to a concrete process. Usually, system out or the message viewer (GUI) are * used for global log messages. */ public static void initGlobalLogging(OutputStream out, int logVerbosity) { getGlobal().init(out, logVerbosity, false, GLOBAL_PREFIX); } /** Initialises the LogService using the given parameters. */ private void init(String fileName, int verbosityLevel, String prefix) { if (fileName == null) { logFile = null; init(System.out, verbosityLevel, false, prefix); } else if (fileName.equals("stderr")) { logFile = null; init(System.err, verbosityLevel, false, prefix); log("Logging: log file name is 'stderr', using system err for logging.", INIT); } else if (fileName.equals("stdout")) { logFile = null; init(System.out, verbosityLevel, false, prefix); log("Logging: log file name is 'stdout', using system out for logging.", INIT); } else { if (process != null) logFile = process.resolveFileName(fileName); else logFile = new File(fileName); OutputStream out = null; try { out = new FileOutputStream(logFile); } catch (IOException e) { log("Cannot create logfile '" + fileName + "': " + e.getClass() + ":" + e.getMessage(), LogService.MAXIMUM); log("using stdout", LogService.MAXIMUM); out = System.out; } init(out, verbosityLevel, false, prefix); log("Logging: log file is '" + logFile.getName() + "'...", INIT); } } /** * Initialises the LogService. * * @param out * The stream to write the messages to. * @param verbosityLevel * Only messages with message.verbosityLevel >= verbosityLevel * are logged * @param format * must be true if the output should be formatted by the * FormattedPrintStream */ private void init(OutputStream out, int verbosityLevel, boolean format, String prefix) { if (format) { this.logOut = new PrintStream(new FormattedFilterStream(out, new DummyLogFormatFilter())); } else { this.logOut = new PrintStream(new FormattedFilterStream(out, new DeleteLogFormatFilter())); } this.prefix = prefix; if (verbosityLevel == UNKNOWN_LEVEL) this.minVerbosityLevel = INIT; else this.minVerbosityLevel = verbosityLevel; lastMessage = ""; equalMessageCount = 0; if (RapidMinerGUI.getMainFrame() != null) { try { initGUI(); } catch (UndefinedParameterError e) { log("Cannot initialize GUI logging: " + e.getMessage(), ERROR); e.printStackTrace(); } } } public void initGUI() throws UndefinedParameterError { if (RapidMinerGUI.getMainFrame() != null) { if (process != null) { this.minVerbosityLevel = process.getRootOperator().getParameterAsInt(ProcessRootOperator.PARAMETER_LOGVERBOSITY); String logFileName = process.getRootOperator().getParameterAsString(ProcessRootOperator.PARAMETER_LOGFILE); this.logFile = process.resolveFileName(logFileName); } else { this.logFile = null; } if (this.logFile != null) { try { this.logOut = new PrintStream(new StreamMultiplier(new FormattedFilterStream(new FileOutputStream(logFile), new DeleteLogFormatFilter()), new FormattedFilterStream(RapidMinerGUI.getMainFrame().getMessageViewer().outputStream, new DummyLogFormatFilter()))); } catch (java.io.IOException e) { throw new RuntimeException("Cannot create log file: " + e); } } else { this.logOut = new PrintStream(RapidMinerGUI.getMainFrame().getMessageViewer().outputStream); } } } /** Closes the stream. ATTENTION: Invoking this method might close System.out / System.err !!! * Don't use it for now (maybe in finalize of LogService objects after they are tied to Process). */ /* public static void close() { if ((!System.out.equals(logOut)) && (!System.err.equals(logOut))) logOut.close(); } */ /** Flush the streams. */ public void flush() { this.logOut.flush(); } public void setVerbosityLevel(int level) { this.minVerbosityLevel = level; } public int getVerbosityLevel() { return this.minVerbosityLevel; } public boolean isSufficientLogVerbosity(int level) { return level >= this.minVerbosityLevel; } // -------------------- Methoden zum Protokollieren -------------------- /** * Writes the message to the output stream if the verbosity level is high * enough. * * @deprecated please do not use this log method any longer, use the method {@link #log(String, int)} instead */ @Deprecated public static void logMessage(String message, int verbosityLevel) { getGlobal().log(message, verbosityLevel); } /** * Writes the message to the output stream if the verbosity level is high * enough. */ public void log(String message, int verbosityLevel) { if (message == null) return; if (verbosityLevel < minVerbosityLevel) return; if (message.equals(lastMessage)) { equalMessageCount++; return; } if (equalMessageCount > 0) { logOut.println("Last message repeated " + equalMessageCount + " times."); equalMessageCount = 0; } lastMessage = message; logOut.println(prefix + " " + getTime() + " " + VERBOSITYLEVEL_START[verbosityLevel] + message + VERBOSITYLEVEL_END[verbosityLevel]); } /** * Writes the message to the output stream if the verbosity level is high * enough and appends the process tree with operator op marked. */ private void logMessageWithTree(String message, int verbosityLevel, Process process, Operator op) { if (verbosityLevel < minVerbosityLevel) return; log(message, verbosityLevel); String treeString = process.getRootOperator().createMarkedProcessTree(10, " here ==> ", op); logOut.println("$m" + treeString + "^m"); } /** Writes the message and the stack trace of the exception. This method should not be used * from operators but only at the end of processes. This will be ensured by RapidMiner itself. * If RapidMiner is not used in debug mode, the stack trace will not be shown. */ public void logFinalException(String message, Process process, Throwable exception, boolean debugMode) { if (FATAL < this.minVerbosityLevel) return; exception.printStackTrace(); Operator op = null; if (process != null) op = process.getCurrentOperator(); if (op != null) { log(Tools.classNameWOPackage(exception.getClass()) + " occured in " + Tools.ordinalNumber(op.getApplyCount()) + " application of " + op, FATAL); logMessageWithTree(message, FATAL, process, op); } else { log(Tools.classNameWOPackage(exception.getClass()) + " occured.", FATAL); log(message, FATAL); } if (!(exception instanceof NoBugError)) { if (debugMode) { log(exception.getMessage(), FATAL); exception.printStackTrace(logOut); } } } /** Returns the current log file or null. */ public File getLogFile() { return logFile; } public void log(String message) { log(message, STATUS); } public void logError(String message) { log(message, ERROR); } public void logNote(String message) { log(message, NOTE); } public void logWarning(String message) { log(message, WARNING); } // -------------------- private helper methods -------------------- /** Returns the current system time nicely formatted. */ private String getTime() { return java.text.DateFormat.getDateTimeInstance().format(new Date()) + ":"; } /** Returns the parsed integer (if the given number represents a number) or the corresponding * verbosity level if the given string is one of the level names or -1. */ private static int getVerbosityLevel(String verbosityString) { if ((verbosityString == null) || (verbosityString.trim().length() == 0)) return UNKNOWN_LEVEL; try { return Integer.parseInt(verbosityString); } catch (NumberFormatException e) { int counter = 0; for (String name : LOG_VERBOSITY_NAMES) { if (name.equals(verbosityString)) return counter; counter++; } } return UNKNOWN_LEVEL; } }