diff options
Diffstat (limited to 'libjava/classpath/java/util/logging')
18 files changed, 6683 insertions, 0 deletions
diff --git a/libjava/classpath/java/util/logging/ConsoleHandler.java b/libjava/classpath/java/util/logging/ConsoleHandler.java new file mode 100644 index 000000000..9f6bb7240 --- /dev/null +++ b/libjava/classpath/java/util/logging/ConsoleHandler.java @@ -0,0 +1,125 @@ +/* ConsoleHandler.java -- a class for publishing log messages to System.err + Copyright (C) 2002, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +/** + * A <code>ConsoleHandler</code> publishes log records to + * <code>System.err</code>. + * + * <p><strong>Configuration:</strong> Values of the subsequent + * <code>LogManager</code> properties are taken into consideration + * when a <code>ConsoleHandler</code> is initialized. + * If a property is not defined, or if it has an invalid + * value, a default is taken without an exception being thrown. + * + * <ul> + * + * <li><code>java.util.logging.ConsoleHandler.level</code> - specifies + * the initial severity level threshold. Default value: + * <code>Level.INFO</code>.</li> + * + * <li><code>java.util.logging.ConsoleHandler.filter</code> - specifies + * the name of a Filter class. Default value: No Filter.</li> + * + * <li><code>java.util.logging.ConsoleHandler.formatter</code> - specifies + * the name of a Formatter class. Default value: + * <code>java.util.logging.SimpleFormatter</code>.</li> + * + * <li><code>java.util.logging.ConsoleHandler.encoding</code> - specifies + * the name of the character encoding. Default value: + * the default platform encoding.</li> + * + * </ul> + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class ConsoleHandler + extends StreamHandler +{ + /** + * Constructs a <code>StreamHandler</code> that publishes + * log records to <code>System.err</code>. The initial + * configuration is determined by the <code>LogManager</code> + * properties described above. + */ + public ConsoleHandler() + { + super(System.err, "java.util.logging.ConsoleHandler", Level.INFO, + /* formatter */ null, SimpleFormatter.class); + } + + + /** + * Forces any data that may have been buffered to the underlying + * output device, but does <i>not</i> close <code>System.err</code>. + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> + * of this <code>ConsoleHandler</code> will be informed, but the caller + * of this method will not receive an exception. + */ + public void close() + { + flush(); + } + + + /** + * Publishes a <code>LogRecord</code> to the console, provided the + * record passes all tests for being loggable. + * + * <p>Most applications do not need to call this method directly. + * Instead, they will use use a <code>Logger</code>, which will + * create LogRecords and distribute them to registered handlers. + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> + * of this <code>SocketHandler</code> will be informed, but the caller + * of this method will not receive an exception. + * + * <p>The GNU implementation of <code>ConsoleHandler.publish</code> + * calls flush() for every request to publish a record, so + * they appear immediately on the console. + * + * @param record the log event to be published. + */ + public void publish(LogRecord record) + { + super.publish(record); + flush(); + } +} diff --git a/libjava/classpath/java/util/logging/ErrorManager.java b/libjava/classpath/java/util/logging/ErrorManager.java new file mode 100644 index 000000000..291efb2f1 --- /dev/null +++ b/libjava/classpath/java/util/logging/ErrorManager.java @@ -0,0 +1,193 @@ +/* ErrorManager.java -- + A class for dealing with errors that a Handler encounters + during logging + Copyright (C) 2002, 2003 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +/** + * An <code>ErrorManager</code> deals with errors that a <code>Handler</code> + * encounters while logging. + * + * @see Handler#setErrorManager(ErrorManager) + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class ErrorManager +{ + /* The values have been taken from Sun's public J2SE 1.4 API + * documentation. + * See http://java.sun.com/j2se/1.4/docs/api/constant-values.html + */ + + /** + * Indicates that there was a failure that does not readily + * fall into any of the other categories. + */ + public static final int GENERIC_FAILURE = 0; + + + /** + * Indicates that there was a problem upon writing to + * an output stream. + */ + public static final int WRITE_FAILURE = 1; + + + /** + * Indicates that there was a problem upon flushing + * an output stream. + */ + public static final int FLUSH_FAILURE = 2; + + + /** + * Indicates that there was a problem upon closing + * an output stream. + */ + public static final int CLOSE_FAILURE = 3; + + + /** + * Indicates that there was a problem upon opening + * an output stream. + */ + public static final int OPEN_FAILURE = 4; + + + /** + * Indicates that there was a problem upon formatting + * the message of a log record. + */ + public static final int FORMAT_FAILURE = 5; + + + /** + * Indicates whether the {@link #error} method of this ErrorManager + * has ever been used. + * + * Declared volatile in order to correctly support the + * double-checked locking idiom (once the revised Java Memory Model + * gets adopted); see Classpath bug #2944. + */ + private volatile boolean everUsed = false; + + + public ErrorManager() + { + } + + + /** + * Reports an error that occured upon logging. The default implementation + * emits the very first error to System.err, ignoring subsequent errors. + * + * @param message a message describing the error, or <code>null</code> if + * there is no suitable description. + * + * @param ex an exception, or <code>null</code> if the error is not + * related to an exception. + * + * @param errorCode one of the defined error codes, for example + * <code>ErrorManager.CLOSE_FAILURE</code>. + */ + public void error(String message, Exception ex, int errorCode) + { + if (everUsed) + return; + + synchronized (this) + { + /* The double check is intentional. If the first check was + * omitted, the monitor would have to be entered every time + * error() method was called. If the second check was + * omitted, the code below could be executed by multiple + * threads simultaneously. + * + * This is the 'double-checked locking' idiom, which is broken + * with the current version of the Java memory model. However, + * we assume that JVMs will have adopted a revised version of + * the Java Memory Model by the time GNU Classpath gains + * widespread acceptance. See Classpath bug #2944. + */ + if (everUsed) + return; + + everUsed = true; + } + + String codeMsg; + switch (errorCode) + { + case GENERIC_FAILURE: + codeMsg = "GENERIC_FAILURE"; + break; + + case WRITE_FAILURE: + codeMsg = "WRITE_FAILURE"; + break; + + case FLUSH_FAILURE: + codeMsg = "FLUSH_FAILURE"; + break; + + case CLOSE_FAILURE: + codeMsg = "CLOSE_FAILURE"; + break; + + case OPEN_FAILURE: + codeMsg = "OPEN_FAILURE"; + break; + + case FORMAT_FAILURE: + codeMsg = "FORMAT_FAILURE"; + break; + + default: + codeMsg = String.valueOf(errorCode); + break; + } + + System.err.println("Error upon logging: " + codeMsg); + if ((message != null) && (message.length() > 0)) + System.err.println(message); + + if (ex != null) + ex.printStackTrace(); + } +} diff --git a/libjava/classpath/java/util/logging/FileHandler.java b/libjava/classpath/java/util/logging/FileHandler.java new file mode 100644 index 000000000..00d967f57 --- /dev/null +++ b/libjava/classpath/java/util/logging/FileHandler.java @@ -0,0 +1,665 @@ +/* FileHandler.java -- a class for publishing log messages to log files + Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import gnu.java.lang.CPStringBuilder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.LinkedList; +import java.util.ListIterator; + +/** + * A <code>FileHandler</code> publishes log records to a set of log + * files. A maximum file size can be specified; as soon as a log file + * reaches the size limit, it is closed and the next file in the set + * is taken. + * + * <p><strong>Configuration:</strong> Values of the subsequent + * <code>LogManager</code> properties are taken into consideration + * when a <code>FileHandler</code> is initialized. If a property is + * not defined, or if it has an invalid value, a default is taken + * without an exception being thrown. + * + * <ul> + * + * <li><code>java.util.FileHandler.level</code> - specifies + * the initial severity level threshold. Default value: + * <code>Level.ALL</code>.</li> + * + * <li><code>java.util.FileHandler.filter</code> - specifies + * the name of a Filter class. Default value: No Filter.</li> + * + * <li><code>java.util.FileHandler.formatter</code> - specifies + * the name of a Formatter class. Default value: + * <code>java.util.logging.XMLFormatter</code>.</li> + * + * <li><code>java.util.FileHandler.encoding</code> - specifies + * the name of the character encoding. Default value: + * the default platform encoding.</li> + * + * <li><code>java.util.FileHandler.limit</code> - specifies the number + * of bytes a log file is approximately allowed to reach before it + * is closed and the handler switches to the next file in the + * rotating set. A value of zero means that files can grow + * without limit. Default value: 0 (unlimited growth).</li> + * + * <li><code>java.util.FileHandler.count</code> - specifies the number + * of log files through which this handler cycles. Default value: + * 1.</li> + * + * <li><code>java.util.FileHandler.pattern</code> - specifies a + * pattern for the location and name of the produced log files. + * See the section on <a href="#filePatterns">file name + * patterns</a> for details. Default value: + * <code>"%h/java%u.log"</code>.</li> + * + * <li><code>java.util.FileHandler.append</code> - specifies + * whether the handler will append log records to existing + * files, or whether the handler will clear log files + * upon switching to them. Default value: <code>false</code>, + * indicating that files will be cleared.</li> + * + * </ul> + * + * <p><a name="filePatterns"><strong>File Name Patterns:</strong></a> + * The name and location and log files are specified with pattern + * strings. The handler will replace the following character sequences + * when opening log files: + * + * <p><ul> + * <li><code>/</code> - replaced by the platform-specific path name + * separator. This value is taken from the system property + * <code>file.separator</code>.</li> + * + * <li><code>%t</code> - replaced by the platform-specific location of + * the directory intended for temporary files. This value is + * taken from the system property <code>java.io.tmpdir</code>.</li> + * + * <li><code>%h</code> - replaced by the location of the home + * directory of the current user. This value is taken from the + * system property <code>user.home</code>.</li> + * + * <li><code>%g</code> - replaced by a generation number for + * distinguisthing the individual items in the rotating set + * of log files. The generation number cycles through the + * sequence 0, 1, ..., <code>count</code> - 1.</li> + * + * <li><code>%u</code> - replaced by a unique number for + * distinguisthing the output files of several concurrently + * running processes. The <code>FileHandler</code> starts + * with 0 when it tries to open a log file. If the file + * cannot be opened because it is currently in use, + * the unique number is incremented by one and opening + * is tried again. These steps are repeated until the + * opening operation succeeds. + * + * <p>FIXME: Is the following correct? Please review. The unique + * number is determined for each log file individually when it is + * opened upon switching to the next file. Therefore, it is not + * correct to assume that all log files in a rotating set bear the + * same unique number. + * + * <p>FIXME: The Javadoc for the Sun reference implementation + * says: "Note that the use of unique ids to avoid conflicts is + * only guaranteed to work reliably when using a local disk file + * system." Why? This needs to be mentioned as well, in case + * the reviewers decide the statement is true. Otherwise, + * file a bug report with Sun.</li> + * + * <li><code>%%</code> - replaced by a single percent sign.</li> + * </ul> + * + * <p>If the pattern string does not contain <code>%g</code> and + * <code>count</code> is greater than one, the handler will append + * the string <code>.%g</code> to the specified pattern. + * + * <p>If the handler attempts to open a log file, this log file + * is being used at the time of the attempt, and the pattern string + * does not contain <code>%u</code>, the handler will append + * the string <code>.%u</code> to the specified pattern. This + * step is performed after any generation number has been + * appended. + * + * <p><em>Examples for the GNU platform:</em> + * + * <p><ul> + * + * <li><code>%h/java%u.log</code> will lead to a single log file + * <code>/home/janet/java0.log</code>, assuming <code>count</code> + * equals 1, the user's home directory is + * <code>/home/janet</code>, and the attempt to open the file + * succeeds.</li> + * + * <li><code>%h/java%u.log</code> will lead to three log files + * <code>/home/janet/java0.log.0</code>, + * <code>/home/janet/java0.log.1</code>, and + * <code>/home/janet/java0.log.2</code>, + * assuming <code>count</code> equals 3, the user's home + * directory is <code>/home/janet</code>, and all attempts + * to open files succeed.</li> + * + * <li><code>%h/java%u.log</code> will lead to three log files + * <code>/home/janet/java0.log.0</code>, + * <code>/home/janet/java1.log.1</code>, and + * <code>/home/janet/java0.log.2</code>, + * assuming <code>count</code> equals 3, the user's home + * directory is <code>/home/janet</code>, and the attempt + * to open <code>/home/janet/java0.log.1</code> fails.</li> + * + * </ul> + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class FileHandler + extends StreamHandler +{ + /** + * A literal that prefixes all file-handler related properties in the + * logging.properties file. + */ + private static final String PROPERTY_PREFIX = "java.util.logging.FileHandler"; + /** + * The name of the property to set for specifying a file naming (incl. path) + * pattern to use with rotating log files. + */ + private static final String PATTERN_KEY = PROPERTY_PREFIX + ".pattern"; + /** + * The default pattern to use when the <code>PATTERN_KEY</code> property was + * not specified in the logging.properties file. + */ + private static final String DEFAULT_PATTERN = "%h/java%u.log"; + /** + * The name of the property to set for specifying an approximate maximum + * amount, in bytes, to write to any one log output file. A value of zero + * (which is the default) implies a no limit. + */ + private static final String LIMIT_KEY = PROPERTY_PREFIX + ".limit"; + private static final int DEFAULT_LIMIT = 0; + /** + * The name of the property to set for specifying how many output files to + * cycle through. The default value is 1. + */ + private static final String COUNT_KEY = PROPERTY_PREFIX + ".count"; + private static final int DEFAULT_COUNT = 1; + /** + * The name of the property to set for specifying whether this handler should + * append, or not, its output to existing files. The default value is + * <code>false</code> meaning NOT to append. + */ + private static final String APPEND_KEY = PROPERTY_PREFIX + ".append"; + private static final boolean DEFAULT_APPEND = false; + + /** + * The number of bytes a log file is approximately allowed to reach + * before it is closed and the handler switches to the next file in + * the rotating set. A value of zero means that files can grow + * without limit. + */ + private final int limit; + + + /** + * The number of log files through which this handler cycles. + */ + private final int count; + + + /** + * The pattern for the location and name of the produced log files. + * See the section on <a href="#filePatterns">file name patterns</a> + * for details. + */ + private final String pattern; + + + /** + * Indicates whether the handler will append log records to existing + * files (<code>true</code>), or whether the handler will clear log files + * upon switching to them (<code>false</code>). + */ + private final boolean append; + + + /** + * The number of bytes that have currently been written to the stream. + * Package private for use in inner classes. + */ + long written; + + + /** + * A linked list of files we are, or have written to. The entries + * are file path strings, kept in the order + */ + private LinkedList logFiles; + + + /** + * Constructs a <code>FileHandler</code>, taking all property values + * from the current {@link LogManager LogManager} configuration. + * + * @throws java.io.IOException FIXME: The Sun Javadoc says: "if + * there are IO problems opening the files." This conflicts + * with the general principle that configuration errors do + * not prohibit construction. Needs review. + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + */ + public FileHandler() + throws IOException, SecurityException + { + this(LogManager.getLogManager().getProperty(PATTERN_KEY), + LogManager.getIntProperty(LIMIT_KEY, DEFAULT_LIMIT), + LogManager.getIntProperty(COUNT_KEY, DEFAULT_COUNT), + LogManager.getBooleanProperty(APPEND_KEY, DEFAULT_APPEND)); + } + + + /* FIXME: Javadoc missing. */ + public FileHandler(String pattern) + throws IOException, SecurityException + { + this(pattern, DEFAULT_LIMIT, DEFAULT_COUNT, DEFAULT_APPEND); + } + + + /* FIXME: Javadoc missing. */ + public FileHandler(String pattern, boolean append) + throws IOException, SecurityException + { + this(pattern, DEFAULT_LIMIT, DEFAULT_COUNT, append); + } + + + /* FIXME: Javadoc missing. */ + public FileHandler(String pattern, int limit, int count) + throws IOException, SecurityException + { + this(pattern, limit, count, + LogManager.getBooleanProperty(APPEND_KEY, DEFAULT_APPEND)); + } + + + /** + * Constructs a <code>FileHandler</code> given the pattern for the + * location and name of the produced log files, the size limit, the + * number of log files thorough which the handler will rotate, and + * the <code>append</code> property. All other property values are + * taken from the current {@link LogManager LogManager} + * configuration. + * + * @param pattern The pattern for the location and name of the + * produced log files. See the section on <a + * href="#filePatterns">file name patterns</a> for details. + * If <code>pattern</code> is <code>null</code>, the value is + * taken from the {@link LogManager LogManager} configuration + * property + * <code>java.util.logging.FileHandler.pattern</code>. + * However, this is a pecularity of the GNU implementation, + * and Sun's API specification does not mention what behavior + * is to be expected for <code>null</code>. Therefore, + * applications should not rely on this feature. + * + * @param limit specifies the number of bytes a log file is + * approximately allowed to reach before it is closed and the + * handler switches to the next file in the rotating set. A + * value of zero means that files can grow without limit. + * + * @param count specifies the number of log files through which this + * handler cycles. + * + * @param append specifies whether the handler will append log + * records to existing files (<code>true</code>), or whether the + * handler will clear log files upon switching to them + * (<code>false</code>). + * + * @throws java.io.IOException FIXME: The Sun Javadoc says: "if + * there are IO problems opening the files." This conflicts + * with the general principle that configuration errors do + * not prohibit construction. Needs review. + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + * <p>FIXME: This seems in contrast to all other handler + * constructors -- verify this by running tests against + * the Sun reference implementation. + */ + public FileHandler(String pattern, + int limit, + int count, + boolean append) + throws IOException, SecurityException + { + super(/* output stream, created below */ null, + PROPERTY_PREFIX, + /* default level */ Level.ALL, + /* formatter */ null, + /* default formatter */ XMLFormatter.class); + + if ((limit <0) || (count < 1)) + throw new IllegalArgumentException(); + + this.pattern = pattern != null ? pattern : DEFAULT_PATTERN; + this.limit = limit; + this.count = count; + this.append = append; + this.written = 0; + this.logFiles = new LinkedList (); + + setOutputStream (createFileStream (this.pattern, limit, count, append, + /* generation */ 0)); + } + + + /* FIXME: Javadoc missing. */ + private OutputStream createFileStream(String pattern, + int limit, + int count, + boolean append, + int generation) + { + String path; + int unique = 0; + + /* Throws a SecurityException if the caller does not have + * LoggingPermission("control"). + */ + LogManager.getLogManager().checkAccess(); + + /* Default value from the java.util.logging.FileHandler.pattern + * LogManager configuration property. + */ + if (pattern == null) + pattern = LogManager.getLogManager().getProperty(PATTERN_KEY); + if (pattern == null) + pattern = DEFAULT_PATTERN; + + if (count > 1 && !has (pattern, 'g')) + pattern = pattern + ".%g"; + + do + { + path = replaceFileNameEscapes(pattern, generation, unique, count); + + try + { + File file = new File(path); + if (!file.exists () || append) + { + FileOutputStream fout = new FileOutputStream (file, append); + // FIXME we need file locks for this to work properly, but they + // are not implemented yet in Classpath! Madness! +// FileChannel channel = fout.getChannel (); +// FileLock lock = channel.tryLock (); +// if (lock != null) // We've locked the file. +// { + if (logFiles.isEmpty ()) + logFiles.addFirst (path); + return new ostr (fout); +// } + } + } + catch (Exception ex) + { + reportError (null, ex, ErrorManager.OPEN_FAILURE); + } + + unique = unique + 1; + if (!has (pattern, 'u')) + pattern = pattern + ".%u"; + } + while (true); + } + + + /** + * Replaces the substrings <code>"/"</code> by the value of the + * system property <code>"file.separator"</code>, <code>"%t"</code> + * by the value of the system property + * <code>"java.io.tmpdir"</code>, <code>"%h"</code> by the value of + * the system property <code>"user.home"</code>, <code>"%g"</code> + * by the value of <code>generation</code>, <code>"%u"</code> by the + * value of <code>uniqueNumber</code>, and <code>"%%"</code> by a + * single percent character. If <code>pattern</code> does + * <em>not</em> contain the sequence <code>"%g"</code>, + * the value of <code>generation</code> will be appended to + * the result. + * + * @throws NullPointerException if one of the system properties + * <code>"file.separator"</code>, + * <code>"java.io.tmpdir"</code>, or + * <code>"user.home"</code> has no value and the + * corresponding escape sequence appears in + * <code>pattern</code>. + */ + private static String replaceFileNameEscapes(String pattern, + int generation, + int uniqueNumber, + int count) + { + CPStringBuilder buf = new CPStringBuilder(pattern); + String replaceWith; + boolean foundGeneration = false; + + int pos = 0; + do + { + // Uncomment the next line for finding bugs. + // System.out.println(buf.substring(0,pos) + '|' + buf.substring(pos)); + + if (buf.charAt(pos) == '/') + { + /* The same value is also provided by java.io.File.separator. */ + replaceWith = System.getProperty("file.separator"); + buf.replace(pos, pos + 1, replaceWith); + pos = pos + replaceWith.length() - 1; + continue; + } + + if (buf.charAt(pos) == '%') + { + switch (buf.charAt(pos + 1)) + { + case 't': + replaceWith = System.getProperty("java.io.tmpdir"); + break; + + case 'h': + replaceWith = System.getProperty("user.home"); + break; + + case 'g': + replaceWith = Integer.toString(generation); + foundGeneration = true; + break; + + case 'u': + replaceWith = Integer.toString(uniqueNumber); + break; + + case '%': + replaceWith = "%"; + break; + + default: + replaceWith = "??"; + break; // FIXME: Throw exception? + } + + buf.replace(pos, pos + 2, replaceWith); + pos = pos + replaceWith.length() - 1; + continue; + } + } + while (++pos < buf.length() - 1); + + if (!foundGeneration && (count > 1)) + { + buf.append('.'); + buf.append(generation); + } + + return buf.toString(); + } + + + /* FIXME: Javadoc missing. */ + public void publish(LogRecord record) + { + if (limit > 0 && written >= limit) + rotate (); + super.publish(record); + flush (); + } + + /** + * Rotates the current log files, possibly removing one if we + * exceed the file count. + */ + private synchronized void rotate () + { + if (logFiles.size () > 0) + { + File f1 = null; + ListIterator lit = null; + + // If we reach the file count, ditch the oldest file. + if (logFiles.size () == count) + { + f1 = new File ((String) logFiles.getLast ()); + f1.delete (); + lit = logFiles.listIterator (logFiles.size () - 1); + } + // Otherwise, move the oldest to a new location. + else + { + String path = replaceFileNameEscapes (pattern, logFiles.size (), + /* unique */ 0, count); + f1 = new File (path); + logFiles.addLast (path); + lit = logFiles.listIterator (logFiles.size () - 1); + } + + // Now rotate the files. + while (lit.hasPrevious ()) + { + String s = (String) lit.previous (); + File f2 = new File (s); + f2.renameTo (f1); + f1 = f2; + } + } + + setOutputStream (createFileStream (pattern, limit, count, append, + /* generation */ 0)); + + // Reset written count. + written = 0; + } + + /** + * Tell if <code>pattern</code> contains the pattern sequence + * with character <code>escape</code>. That is, if <code>escape</code> + * is 'g', this method returns true if the given pattern contains + * "%g", and not just the substring "%g" (for example, in the case of + * "%%g"). + * + * @param pattern The pattern to test. + * @param escape The escape character to search for. + * @return True iff the pattern contains the escape sequence with the + * given character. + */ + private static boolean has (final String pattern, final char escape) + { + final int len = pattern.length (); + boolean sawPercent = false; + for (int i = 0; i < len; i++) + { + char c = pattern.charAt (i); + if (sawPercent) + { + if (c == escape) + return true; + if (c == '%') // Double percent + { + sawPercent = false; + continue; + } + } + sawPercent = (c == '%'); + } + return false; + } + + /** + * An output stream that tracks the number of bytes written to it. + */ + private final class ostr extends FilterOutputStream + { + private ostr (OutputStream out) + { + super (out); + } + + public void write (final int b) throws IOException + { + out.write (b); + FileHandler.this.written++; // FIXME: synchronize? + } + + public void write (final byte[] b) throws IOException + { + write (b, 0, b.length); + } + + public void write (final byte[] b, final int offset, final int length) + throws IOException + { + out.write (b, offset, length); + FileHandler.this.written += length; // FIXME: synchronize? + } + } +} diff --git a/libjava/classpath/java/util/logging/Filter.java b/libjava/classpath/java/util/logging/Filter.java new file mode 100644 index 000000000..ec4597670 --- /dev/null +++ b/libjava/classpath/java/util/logging/Filter.java @@ -0,0 +1,64 @@ +/* Filter.java -- an interface for filters that decide whether a + LogRecord should be published or discarded + Copyright (C) 2002, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +/** + * By implementing the <code>Filter</code> interface, applications + * can control what is being logged based on arbitrary properties, + * not just the severity level. Both <code>Handler</code> and + * <code>Logger</code> allow to register Filters whose + * <code>isLoggable</code> method will be called when a + * <code>LogRecord</code> has passed the test based on the + * severity level. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public interface Filter +{ + /** + * Determines whether a LogRecord should be published or discarded. + * + * @param record the <code>LogRecord</code> to be inspected. + * + * @return <code>true</code> if the record should be published, + * <code>false</code> if it should be discarded. + */ + boolean isLoggable(LogRecord record); +} diff --git a/libjava/classpath/java/util/logging/Formatter.java b/libjava/classpath/java/util/logging/Formatter.java new file mode 100644 index 000000000..feaf55315 --- /dev/null +++ b/libjava/classpath/java/util/logging/Formatter.java @@ -0,0 +1,171 @@ +/* Formatter.java -- + A class for formatting log messages by localizing message texts + and performing substitution of parameters + Copyright (C) 2002, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import java.text.MessageFormat; +import java.util.ResourceBundle; + +/** + * A <code>Formatter</code> supports handlers by localizing + * message texts and by subsituting parameter values for their + * placeholders. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public abstract class Formatter +{ + /** + * Constructs a new Formatter. + */ + protected Formatter() + { + } + + + /** + * Formats a LogRecord into a string. Usually called by handlers + * which need a string for a log record, for example to append + * a record to a log file or to transmit a record over the network. + * + * @param record the log record for which a string form is requested. + */ + public abstract String format(LogRecord record); + + + /** + * Returns a string that handlers are supposed to emit before + * the first log record. The base implementation returns an + * empty string, but subclasses such as {@link XMLFormatter} + * override this method in order to provide a suitable header. + * + * @return a string for the header. + * + * @param handler the handler which will prepend the returned + * string in front of the first log record. This method + * may inspect certain properties of the handler, for + * example its encoding, in order to construct the header. + */ + public String getHead(Handler handler) + { + return ""; + } + + + /** + * Returns a string that handlers are supposed to emit after + * the last log record. The base implementation returns an + * empty string, but subclasses such as {@link XMLFormatter} + * override this method in order to provide a suitable tail. + * + * @return a string for the header. + * + * @param handler the handler which will append the returned + * string after the last log record. This method + * may inspect certain properties of the handler + * in order to construct the tail. + */ + public String getTail(Handler handler) + { + return ""; + } + + + /** + * Formats the message part of a log record. + * + * <p>First, the Formatter localizes the record message to the + * default locale by looking up the message in the record's + * localization resource bundle. If this step fails because there + * is no resource bundle associated with the record, or because the + * record message is not a key in the bundle, the raw message is + * used instead. + * + * <p>Second, the Formatter substitutes appropriate strings for + * the message parameters. If the record returns a non-empty + * array for <code>getParameters()</code> and the localized + * message string contains the character sequence "{0", the + * formatter uses <code>java.text.MessageFormat</code> to format + * the message. Otherwise, no parameter substitution is performed. + * + * @param record the log record to be localized and formatted. + * + * @return the localized message text where parameters have been + * substituted by suitable strings. + * + * @throws NullPointerException if <code>record</code> + * is <code>null</code>. + */ + public String formatMessage(LogRecord record) + { + String msg; + ResourceBundle bundle; + Object[] params; + + /* This will throw a NullPointerExceptionif record is null. */ + msg = record.getMessage(); + if (msg == null) + msg = ""; + + /* Try to localize the message. */ + bundle = record.getResourceBundle(); + if (bundle != null) + { + try + { + msg = bundle.getString(msg); + } + catch (java.util.MissingResourceException _) + { + } + } + + /* Format the message if there are parameters. */ + params = record.getParameters(); + if ((params != null) + && (params.length > 0) + && (msg.indexOf("{0") >= 0)) + { + msg = MessageFormat.format(msg, params); + } + + return msg; + } +} diff --git a/libjava/classpath/java/util/logging/Handler.java b/libjava/classpath/java/util/logging/Handler.java new file mode 100644 index 000000000..e254b4d35 --- /dev/null +++ b/libjava/classpath/java/util/logging/Handler.java @@ -0,0 +1,386 @@ +/* Handler.java -- a class for publishing log messages + Copyright (C) 2002, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import java.io.UnsupportedEncodingException; + +/** + * A <code>Handler</code> publishes <code>LogRecords</code> to + * a sink, for example a file, the console or a network socket. + * There are different subclasses of <code>Handler</code> + * to deal with different kinds of sinks. + * + * <p>FIXME: Are handlers thread-safe, or is the assumption that only + * loggers are, and a handler can belong only to one single logger? If + * the latter, should we enforce it? (Spec not clear). In any + * case, it needs documentation. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public abstract class Handler +{ + Formatter formatter; + Filter filter; + Level level; + ErrorManager errorManager; + String encoding; + + /** + * Constructs a Handler with a logging severity level of + * <code>Level.ALL</code>, no formatter, no filter, and + * an instance of <code>ErrorManager</code> managing errors. + * + * <p><strong>Specification Note:</strong> The specification of the + * Java<sup>TM</sup> Logging API does not mention which character + * encoding is to be used by freshly constructed Handlers. The GNU + * implementation uses the default platform encoding, but other + * Java implementations might behave differently. + * + * <p><strong>Specification Note:</strong> While a freshly constructed + * Handler is required to have <em>no filter</em> according to the + * specification, <code>null</code> is not a valid parameter for + * <code>Handler.setFormatter</code>. Therefore, the following + * code will throw a <code>java.lang.NullPointerException</code>: + * + * <p><pre>Handler h = new MyConcreteSubclassOfHandler(); +h.setFormatter(h.getFormatter());</pre> + * + * It seems strange that a freshly constructed Handler is not + * supposed to provide a Formatter, but this is what the specification + * says. + */ + protected Handler() + { + level = Level.ALL; + } + + + /** + * Publishes a <code>LogRecord</code> to an appropriate sink, + * provided the record passes all tests for being loggable. The + * <code>Handler</code> will localize the message of the log + * record and substitute any message parameters. + * + * <p>Most applications do not need to call this method directly. + * Instead, they will use use a {@link Logger}, which will + * create LogRecords and distribute them to registered handlers. + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> + * of this <code>Handler</code> will be informed, but the caller + * of this method will not receive an exception. + * + * @param record the log event to be published. + */ + public abstract void publish(LogRecord record); + + + /** + * Forces any data that may have been buffered to the underlying + * output device. + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> + * of this <code>Handler</code> will be informed, but the caller + * of this method will not receive an exception. + */ + public abstract void flush(); + + + /** + * Closes this <code>Handler</code> after having flushed + * the buffers. As soon as <code>close</code> has been called, + * a <code>Handler</code> should not be used anymore. Attempts + * to publish log records, to flush buffers, or to modify the + * <code>Handler</code> in any other way may throw runtime + * exceptions after calling <code>close</code>. + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> + * of this <code>Handler</code> will be informed, but the caller + * of this method will not receive an exception. + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + */ + public abstract void close() + throws SecurityException; + + + /** + * Returns the <code>Formatter</code> which will be used to + * localize the text of log messages and to substitute + * message parameters. A <code>Handler</code> is encouraged, + * but not required to actually use an assigned + * <code>Formatter</code>. + * + * @return the <code>Formatter</code> being used, or + * <code>null</code> if this <code>Handler</code> + * does not use formatters and no formatter has + * ever been set by calling <code>setFormatter</code>. + */ + public Formatter getFormatter() + { + return formatter; + } + + + /** + * Sets the <code>Formatter</code> which will be used to + * localize the text of log messages and to substitute + * message parameters. A <code>Handler</code> is encouraged, + * but not required to actually use an assigned + * <code>Formatter</code>. + * + * @param formatter the new <code>Formatter</code> to use. + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + * + * @throws NullPointerException if <code>formatter</code> is + * <code>null</code>. + */ + public void setFormatter(Formatter formatter) + throws SecurityException + { + LogManager.getLogManager().checkAccess(); + + /* Throws a NullPointerException if formatter is null. */ + formatter.getClass(); + + this.formatter = formatter; + } + + + /** + * Returns the character encoding which this handler uses for publishing + * log records. + * + * @return the name of a character encoding, or <code>null</code> + * for the default platform encoding. + */ + public String getEncoding() + { + return encoding; + } + + + /** + * Sets the character encoding which this handler uses for publishing + * log records. The encoding of a <code>Handler</code> must be + * set before any log records have been published. + * + * @param encoding the name of a character encoding, or <code>null</code> + * for the default encoding. + * + * @exception SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + * + */ + public void setEncoding(String encoding) + throws SecurityException, UnsupportedEncodingException + { + /* Should any developer ever change this implementation, they are + * advised to have a look at StreamHandler.setEncoding(String), + * which overrides this method without calling super.setEncoding. + */ + LogManager.getLogManager().checkAccess(); + + /* Simple check for supported encodings. This is more expensive + * than it could be, but this method is overwritten by StreamHandler + * anyway. + */ + if (encoding != null) + new String(new byte[0], encoding); + + this.encoding = encoding; + } + + + /** + * Returns the <code>Filter</code> that currently controls which + * log records are being published by this <code>Handler</code>. + * + * @return the currently active <code>Filter</code>, or + * <code>null</code> if no filter has been associated. + * In the latter case, log records are filtered purely + * based on their severity level. + */ + public Filter getFilter() + { + return filter; + } + + + /** + * Sets the <code>Filter</code> for controlling which + * log records will be published by this <code>Handler</code>. + * + * @param filter the <code>Filter</code> to use, or + * <code>null</code> to filter log records purely based + * on their severity level. + */ + public void setFilter(Filter filter) + throws SecurityException + { + LogManager.getLogManager().checkAccess(); + this.filter = filter; + } + + + /** + * Returns the <code>ErrorManager</code> that currently deals + * with errors originating from this Handler. + * + * @exception SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + */ + public ErrorManager getErrorManager() + { + LogManager.getLogManager().checkAccess(); + + /* Developers wanting to change the subsequent code should + * have a look at Handler.reportError -- it also can create + * an ErrorManager, but does so without checking permissions + * to control the logging infrastructure. + */ + if (errorManager == null) + errorManager = new ErrorManager(); + + return errorManager; + } + + + public void setErrorManager(ErrorManager manager) + { + LogManager.getLogManager().checkAccess(); + + /* Make sure manager is not null. */ + manager.getClass(); + + this.errorManager = manager; + } + + + protected void reportError(String message, Exception ex, int code) + { + if (errorManager == null) + errorManager = new ErrorManager(); + + errorManager.error(message, ex, code); + } + + + /** + * Returns the severity level threshold for this <code>Handler</code> + * All log records with a lower severity level will be discarded; + * a log record of the same or a higher level will be published + * unless an installed <code>Filter</code> decides to discard it. + * + * @return the severity level below which all log messages + * will be discarded. + */ + public Level getLevel() + { + return level; + } + + + /** + * Sets the severity level threshold for this <code>Handler</code>. + * All log records with a lower severity level will be discarded; + * a log record of the same or a higher level will be published + * unless an installed <code>Filter</code> decides to discard it. + * + * @param level the severity level below which all log messages + * will be discarded. + * + * @exception SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + * + * @exception NullPointerException if <code>level</code> is + * <code>null</code>. + */ + public void setLevel(Level level) + { + LogManager.getLogManager().checkAccess(); + + /* Throw NullPointerException if level is null. */ + level.getClass(); + this.level = level; + } + + + /** + * Checks whether a <code>LogRecord</code> would be logged + * if it was passed to this <code>Handler</code> for publication. + * + * <p>The <code>Handler</code> implementation considers a record as + * loggable if its level is greater than or equal to the severity + * level threshold. In a second step, if a {@link Filter} has + * been installed, its {@link Filter#isLoggable(LogRecord) isLoggable} + * method is invoked. Subclasses of <code>Handler</code> can override + * this method to impose their own constraints. + * + * @param record the <code>LogRecord</code> to be checked. + * + * @return <code>true</code> if <code>record</code> would + * be published by {@link #publish(LogRecord) publish}, + * <code>false</code> if it would be discarded. + * + * @see #setLevel(Level) + * @see #setFilter(Filter) + * @see Filter#isLoggable(LogRecord) + * + * @throws NullPointerException if <code>record</code> + * is <code>null</code>. + */ + public boolean isLoggable(LogRecord record) + { + if (record.getLevel().intValue() < level.intValue()) + return false; + + if (filter != null) + return filter.isLoggable(record); + else + return true; + } +} diff --git a/libjava/classpath/java/util/logging/Level.java b/libjava/classpath/java/util/logging/Level.java new file mode 100644 index 000000000..ea2c83fb2 --- /dev/null +++ b/libjava/classpath/java/util/logging/Level.java @@ -0,0 +1,416 @@ +/* Level.java -- a class for indicating logging levels + Copyright (C) 2002, 2005, 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import java.io.Serializable; +import java.util.ResourceBundle; + +/** + * A class for indicating logging levels. A number of commonly used + * levels is pre-defined (such as <code>java.util.logging.Level.INFO</code>), + * and applications should utilize those whenever possible. For specialized + * purposes, however, applications can sub-class Level in order to define + * custom logging levels. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class Level implements Serializable +{ + /* The integer values are the same as in the Sun J2SE 1.4. + * They have been obtained with a test program. In J2SE 1.4.1, + * Sun has amended the API documentation; these values are now + * publicly documented. + */ + + /** + * The <code>OFF</code> level is used as a threshold for filtering + * log records, meaning that no message should be logged. + * + * @see Logger#setLevel(java.util.logging.Level) + */ + public static final Level OFF = new Level ("OFF", Integer.MAX_VALUE); + + /** + * Log records whose level is <code>SEVERE</code> indicate a serious + * failure that prevents normal program execution. Messages at this + * level should be understandable to an inexperienced, non-technical + * end user. Ideally, they explain in simple words what actions the + * user can take in order to resolve the problem. + */ + public static final Level SEVERE = new Level ("SEVERE", 1000); + + + /** + * Log records whose level is <code>WARNING</code> indicate a + * potential problem that does not prevent normal program execution. + * Messages at this level should be understandable to an + * inexperienced, non-technical end user. Ideally, they explain in + * simple words what actions the user can take in order to resolve + * the problem. + */ + public static final Level WARNING = new Level ("WARNING", 900); + + + /** + * Log records whose level is <code>INFO</code> are used in purely + * informational situations that do not constitute serious errors or + * potential problems. In the default logging configuration, INFO + * messages will be written to the system console. For this reason, + * the INFO level should be used only for messages that are + * important to end users and system administrators. Messages at + * this level should be understandable to an inexperienced, + * non-technical user. + */ + public static final Level INFO = new Level ("INFO", 800); + + + /** + * Log records whose level is <code>CONFIG</code> are used for + * describing the static configuration, for example the windowing + * environment, the operating system version, etc. + */ + public static final Level CONFIG = new Level ("CONFIG", 700); + + + /** + * Log records whose level is <code>FINE</code> are typically used + * for messages that are relevant for developers using + * the component generating log messages. Examples include minor, + * recoverable failures, or possible inefficiencies. + */ + public static final Level FINE = new Level ("FINE", 500); + + + /** + * Log records whose level is <code>FINER</code> are intended for + * rather detailed tracing, for example entering a method, returning + * from a method, or throwing an exception. + */ + public static final Level FINER = new Level ("FINER", 400); + + + /** + * Log records whose level is <code>FINEST</code> are used for + * highly detailed tracing, for example to indicate that a certain + * point inside the body of a method has been reached. + */ + public static final Level FINEST = new Level ("FINEST", 300); + + + /** + * The <code>ALL</code> level is used as a threshold for filtering + * log records, meaning that every message should be logged. + * + * @see Logger#setLevel(java.util.logging.Level) + */ + public static final Level ALL = new Level ("ALL", Integer.MIN_VALUE); + + + private static final Level[] knownLevels = { + ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF + }; + + + /** + * The name of the Level without localizing it, for example + * "WARNING". + */ + private String name; + + + /** + * The integer value of this <code>Level</code>. + */ + private int value; + + + /** + * The name of the resource bundle used for localizing the level + * name, or <code>null</code> if the name does not undergo + * localization. + */ + private String resourceBundleName; + + + /** + * Creates a logging level given a name and an integer value. + * It rarely is necessary to create custom levels, + * as most applications should be well served with one of the + * standard levels such as <code>Level.CONFIG</code>, + * <code>Level.INFO</code>, or <code>Level.FINE</code>. + * + * @param name the name of the level. + * + * @param value the integer value of the level. Please note + * that the Java<small><sup>TM</sup></small> + * Logging API does not specify integer + * values for standard levels (such as + * Level.FINE). Therefore, a custom + * level should pass an integer value that + * is calculated at run-time, e.g. + * <code>(Level.FINE.intValue() + Level.CONFIG.intValue()) + * / 2</code> for a level between FINE and CONFIG. + */ + protected Level(String name, int value) + { + this(name, value, null); + } + + + /** + * Create a logging level given a name, an integer value and a name + * of a resource bundle for localizing the level name. It rarely + * is necessary to create custom levels, as most applications + * should be well served with one of the standard levels such as + * <code>Level.CONFIG</code>, <code>Level.INFO</code>, or + * <code>Level.FINE</code>. + * + * @param name the name of the level. + * + * @param value the integer value of the level. Please note + * that the Java<small><sup>TM</sup></small> + * Logging API does not specify integer + * values for standard levels (such as + * Level.FINE). Therefore, a custom + * level should pass an integer value that + * is calculated at run-time, e.g. + * <code>(Level.FINE.intValue() + Level.CONFIG.intValue()) + * / 2</code> for a level between FINE and CONFIG. + * + * @param resourceBundleName the name of a resource bundle + * for localizing the level name, or <code>null</code> + * if the name does not need to be localized. + */ + protected Level(String name, int value, String resourceBundleName) + { + this.name = name; + this.value = value; + this.resourceBundleName = resourceBundleName; + } + + + static final long serialVersionUID = -8176160795706313070L; + + + /** + * Checks whether the Level has the same intValue as one of the + * pre-defined levels. If so, the pre-defined level object is + * returned. + * + * <br/>Since the resource bundle name is not taken into + * consideration, it is possible to resolve Level objects that have + * been de-serialized by another implementation, even if the other + * implementation uses a different resource bundle for localizing + * the names of pre-defined levels. + */ + private Object readResolve() + { + for (int i = 0; i < knownLevels.length; i++) + if (value == knownLevels[i].intValue()) + return knownLevels[i]; + + return this; + } + + + /** + * Returns the name of the resource bundle used for localizing the + * level name. + * + * @return the name of the resource bundle used for localizing the + * level name, or <code>null</code> if the name does not undergo + * localization. + */ + public String getResourceBundleName() + { + return resourceBundleName; + } + + + /** + * Returns the name of the Level without localizing it, for example + * "WARNING". + */ + public String getName() + { + return name; + } + + + /** + * Returns the name of the Level after localizing it, for example + * "WARNUNG". + */ + public String getLocalizedName() + { + String localizedName = null; + + if (resourceBundleName != null) + { + try + { + ResourceBundle b = ResourceBundle.getBundle(resourceBundleName); + localizedName = b.getString(name); + } + catch (Exception _) + { + } + } + + if (localizedName != null) + return localizedName; + else + return name; + } + + + /** + * Returns the name of the Level without localizing it, for example + * "WARNING". + */ + public final String toString() + { + return getName(); + } + + + /** + * Returns the integer value of the Level. + */ + public final int intValue() + { + return value; + } + + + /** + * Returns one of the standard Levels given either its name or its + * integer value. Custom subclasses of Level will not be returned + * by this method. + * + * @throws IllegalArgumentException if <code>name</code> is neither + * the name nor the integer value of one of the pre-defined standard + * logging levels. + * + * @throws NullPointerException if <code>name</code> is null. + * + */ + public static Level parse(String name) + throws IllegalArgumentException + { + /* This will throw a NullPointerException if name is null, + * as required by the API specification. + */ + name = name.intern(); + + for (int i = 0; i < knownLevels.length; i++) + { + // It's safe to use == instead of .equals here because only the + // standard logging levels will be returned by this method, and + // they are all created using string literals. + if (name == knownLevels[i].name) + return knownLevels[i]; + } + + try + { + int num = Integer.parseInt(name); + for (int i = 0; i < knownLevels.length; i++) + if (num == knownLevels[i].value) + return knownLevels[i]; + } + catch (NumberFormatException _) + { + } + + String msg = "Not the name of a standard logging level: \"" + name + "\""; + throw new IllegalArgumentException(msg); + } + + + /** + * Checks whether this Level's integer value is equal to that of + * another object. + * + * @return <code>true</code> if <code>other</code> is an instance of + * <code>java.util.logging.Level</code> and has the same integer + * value, <code>false</code> otherwise. + */ + public boolean equals(Object other) + { + if (!(other instanceof Level)) + return false; + + return value == ((Level) other).value; + } + + + /** + * Returns a hash code for this Level which is based on its numeric + * value. + */ + public int hashCode() + { + return value; + } + + + /** + * Determines whether or not this Level is one of the standard + * levels specified in the Logging API. + * + * <p>This method is package-private because it is not part + * of the logging API specification. However, an XMLFormatter + * is supposed to emit the numeric value for a custom log + * level, but the name for a pre-defined level. It seems + * cleaner to put this method to Level than to write some + * procedural code for XMLFormatter. + * + * @return <code>true</code> if this Level is a standard level, + * <code>false</code> otherwise. + */ + final boolean isStandardLevel() + { + for (int i = 0; i < knownLevels.length; i++) + if (knownLevels[i] == this) + return true; + + return false; + } +} diff --git a/libjava/classpath/java/util/logging/LogManager.java b/libjava/classpath/java/util/logging/LogManager.java new file mode 100644 index 000000000..dffa44d9c --- /dev/null +++ b/libjava/classpath/java/util/logging/LogManager.java @@ -0,0 +1,986 @@ +/* LogManager.java -- a class for maintaining Loggers and managing + configuration properties + Copyright (C) 2002, 2005, 2006, 2007 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import gnu.classpath.SystemProperties; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * The <code>LogManager</code> maintains a hierarchical namespace + * of Logger objects and manages properties for configuring the logging + * framework. There exists only one single <code>LogManager</code> + * per virtual machine. This instance can be retrieved using the + * static method {@link #getLogManager()}. + * + * <p><strong>Configuration Process:</strong> The global LogManager + * object is created and configured when the class + * <code>java.util.logging.LogManager</code> is initialized. + * The configuration process includes the subsequent steps: + * + * <ul> + * <li>If the system property <code>java.util.logging.manager</code> + * is set to the name of a subclass of + * <code>java.util.logging.LogManager</code>, an instance of + * that subclass is created and becomes the global LogManager. + * Otherwise, a new instance of LogManager is created.</li> + * <li>The <code>LogManager</code> constructor tries to create + * a new instance of the class specified by the system + * property <code>java.util.logging.config.class</code>. + * Typically, the constructor of this class will call + * <code>LogManager.getLogManager().readConfiguration(java.io.InputStream)</code> + * for configuring the logging framework. + * The configuration process stops at this point if + * the system property <code>java.util.logging.config.class</code> + * is set (irrespective of whether the class constructor + * could be called or an exception was thrown).</li> + * + * <li>If the system property <code>java.util.logging.config.class</code> + * is <em>not</em> set, the configuration parameters are read in from + * a file and passed to + * {@link #readConfiguration(java.io.InputStream)}. + * The name and location of this file are specified by the system + * property <code>java.util.logging.config.file</code>.</li> + * <li>If the system property <code>java.util.logging.config.file</code> + * is not set, however, the contents of the URL + * "{gnu.classpath.home.url}/logging.properties" are passed to + * {@link #readConfiguration(java.io.InputStream)}. + * Here, "{gnu.classpath.home.url}" stands for the value of + * the system property <code>gnu.classpath.home.url</code>.</li> + * </ul> + * + * <p>The <code>LogManager</code> has a level of <code>INFO</code> by + * default, and this will be inherited by <code>Logger</code>s unless they + * override it either by properties or programmatically. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class LogManager +{ + /** + * The object name for the logging management bean. + * @since 1.5 + */ + public static final String LOGGING_MXBEAN_NAME + = "java.util.logging:type=Logging"; + + /** + * The singleton LogManager instance. + */ + private static LogManager logManager; + + /** + * The singleton logging bean. + */ + private static LoggingMXBean loggingBean; + + /** + * The registered named loggers; maps the name of a Logger to + * a WeakReference to it. + */ + private Map<String, WeakReference<Logger>> loggers; + + /** + * The properties for the logging framework which have been + * read in last. + */ + private Properties properties; + + /** + * A delegate object that provides support for handling + * PropertyChangeEvents. The API specification does not + * mention which bean should be the source in the distributed + * PropertyChangeEvents, but Mauve test code has determined that + * the Sun J2SE 1.4 reference implementation uses the LogManager + * class object. This is somewhat strange, as the class object + * is not the bean with which listeners have to register, but + * there is no reason for the GNU Classpath implementation to + * behave differently from the reference implementation in + * this case. + */ + private final PropertyChangeSupport pcs = new PropertyChangeSupport( /* source bean */ + LogManager.class); + + protected LogManager() + { + loggers = new HashMap(); + } + + /** + * Returns the globally shared LogManager instance. + */ + public static synchronized LogManager getLogManager() + { + if (logManager == null) + { + logManager = makeLogManager(); + initLogManager(); + } + return logManager; + } + + private static final String MANAGER_PROPERTY = "java.util.logging.manager"; + + private static LogManager makeLogManager() + { + String managerClassName = SystemProperties.getProperty(MANAGER_PROPERTY); + LogManager manager = (LogManager) createInstance + (managerClassName, LogManager.class, MANAGER_PROPERTY); + if (manager == null) + manager = new LogManager(); + return manager; + } + + private static final String CONFIG_PROPERTY = "java.util.logging.config.class"; + + private static void initLogManager() + { + LogManager manager = getLogManager(); + Logger.root.setLevel(Level.INFO); + manager.addLogger(Logger.root); + + /* The Javadoc description of the class explains + * what is going on here. + */ + Object configurator = createInstance(System.getProperty(CONFIG_PROPERTY), + /* must be instance of */ Object.class, + CONFIG_PROPERTY); + + try + { + if (configurator == null) + manager.readConfiguration(); + } + catch (IOException ex) + { + /* FIXME: Is it ok to ignore exceptions here? */ + } + } + + /** + * Registers a listener which will be notified when the + * logging properties are re-read. + */ + public synchronized void addPropertyChangeListener(PropertyChangeListener listener) + { + /* do not register null. */ + listener.getClass(); + + pcs.addPropertyChangeListener(listener); + } + + /** + * Unregisters a listener. + * + * If <code>listener</code> has not been registered previously, + * nothing happens. Also, no exception is thrown if + * <code>listener</code> is <code>null</code>. + */ + public synchronized void removePropertyChangeListener(PropertyChangeListener listener) + { + if (listener != null) + pcs.removePropertyChangeListener(listener); + } + + /** + * Adds a named logger. If a logger with the same name has + * already been registered, the method returns <code>false</code> + * without adding the logger. + * + * <p>The <code>LogManager</code> only keeps weak references + * to registered loggers. Therefore, names can become available + * after automatic garbage collection. + * + * @param logger the logger to be added. + * + * @return <code>true</code>if <code>logger</code> was added, + * <code>false</code> otherwise. + * + * @throws NullPointerException if <code>name</code> is + * <code>null</code>. + */ + public synchronized boolean addLogger(Logger logger) + { + /* To developers thinking about to remove the 'synchronized' + * declaration from this method: Please read the comment + * in java.util.logging.Logger.getLogger(String, String) + * and make sure that whatever you change wrt. synchronization + * does not endanger thread-safety of Logger.getLogger. + * The current implementation of Logger.getLogger assumes + * that LogManager does its synchronization on the globally + * shared instance of LogManager. + */ + String name; + WeakReference ref; + + /* This will throw a NullPointerException if logger is null, + * as required by the API specification. + */ + name = logger.getName(); + + ref = loggers.get(name); + if (ref != null) + { + if (ref.get() != null) + return false; + + /* There has been a logger under this name in the past, + * but it has been garbage collected. + */ + loggers.remove(ref); + } + + /* Adding a named logger requires a security permission. */ + if ((name != null) && ! name.equals("")) + checkAccess(); + + Logger parent = findAncestor(logger); + loggers.put(name, new WeakReference<Logger>(logger)); + if (parent != logger.getParent()) + logger.setParent(parent); + + // The level of the newly added logger must be specified. + // The easiest case is if there is a level for exactly this logger + // in the properties. If no such level exists the level needs to be + // searched along the hirachy. So if there is a new logger 'foo.blah.blub' + // and an existing parent logger 'foo' the properties 'foo.blah.blub.level' + // and 'foo.blah.level' need to be checked. If both do not exist in the + // properties the level of the new logger is set to 'null' (i.e. it uses the + // level of its parent 'foo'). + Level logLevel = logger.getLevel(); + String searchName = name; + String parentName = parent != null ? parent.getName() : ""; + while (logLevel == null && ! searchName.equals(parentName)) + { + logLevel = getLevelProperty(searchName + ".level", logLevel); + int index = searchName.lastIndexOf('.'); + if(index > -1) + searchName = searchName.substring(0,index); + else + searchName = ""; + } + logger.setLevel(logLevel); + + /* It can happen that existing loggers should be children of + * the newly added logger. For example, assume that there + * already exist loggers under the names "", "foo", and "foo.bar.baz". + * When adding "foo.bar", the logger "foo.bar.baz" should change + * its parent to "foo.bar". + */ + for (Iterator iter = loggers.keySet().iterator(); iter.hasNext();) + { + Logger possChild = (Logger) ((WeakReference) loggers.get(iter.next())) + .get(); + if ((possChild == null) || (possChild == logger) + || (possChild.getParent() != parent)) + continue; + + if (! possChild.getName().startsWith(name)) + continue; + + if (possChild.getName().charAt(name.length()) != '.') + continue; + + possChild.setParent(logger); + } + + return true; + } + + /** + * Finds the closest ancestor for a logger among the currently + * registered ones. For example, if the currently registered + * loggers have the names "", "foo", and "foo.bar", the result for + * "foo.bar.baz" will be the logger whose name is "foo.bar". + * + * @param child a logger for whose name no logger has been + * registered. + * + * @return the closest ancestor for <code>child</code>, + * or <code>null</code> if <code>child</code> + * is the root logger. + * + * @throws NullPointerException if <code>child</code> + * is <code>null</code>. + */ + private synchronized Logger findAncestor(Logger child) + { + String childName = child.getName(); + int childNameLength = childName.length(); + Logger best = Logger.root; + int bestNameLength = 0; + + Logger cand; + int candNameLength; + + if (child == Logger.root) + return null; + + for (String candName : loggers.keySet()) + { + candNameLength = candName.length(); + + if (candNameLength > bestNameLength + && childNameLength > candNameLength + && childName.startsWith(candName) + && childName.charAt(candNameLength) == '.') + { + cand = loggers.get(candName).get(); + if ((cand == null) || (cand == child)) + continue; + + bestNameLength = candName.length(); + best = cand; + } + } + + return best; + } + + /** + * Returns a Logger given its name. + * + * @param name the name of the logger. + * + * @return a named Logger, or <code>null</code> if there is no + * logger with that name. + * + * @throw java.lang.NullPointerException if <code>name</code> + * is <code>null</code>. + */ + public synchronized Logger getLogger(String name) + { + WeakReference<Logger> ref; + + /* Throw a NullPointerException if name is null. */ + name.getClass(); + + ref = loggers.get(name); + if (ref != null) + return ref.get(); + else + return null; + } + + /** + * Returns an Enumeration of currently registered Logger names. + * Since other threads can register loggers at any time, the + * result could be different any time this method is called. + * + * @return an Enumeration with the names of the currently + * registered Loggers. + */ + public synchronized Enumeration<String> getLoggerNames() + { + return Collections.enumeration(loggers.keySet()); + } + + /** + * Resets the logging configuration by removing all handlers for + * registered named loggers and setting their level to <code>null</code>. + * The level of the root logger will be set to <code>Level.INFO</code>. + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + */ + public synchronized void reset() throws SecurityException + { + /* Throw a SecurityException if the caller does not have the + * permission to control the logging infrastructure. + */ + checkAccess(); + + properties = new Properties(); + + Iterator<WeakReference<Logger>> iter = loggers.values().iterator(); + while (iter.hasNext()) + { + WeakReference<Logger> ref; + Logger logger; + + ref = iter.next(); + if (ref != null) + { + logger = ref.get(); + + if (logger == null) + iter.remove(); + else if (logger != Logger.root) + { + logger.resetLogger(); + logger.setLevel(null); + } + } + } + + Logger.root.setLevel(Level.INFO); + Logger.root.resetLogger(); + } + + /** + * Configures the logging framework by reading a configuration file. + * The name and location of this file are specified by the system + * property <code>java.util.logging.config.file</code>. If this + * property is not set, the URL + * "{gnu.classpath.home.url}/logging.properties" is taken, where + * "{gnu.classpath.home.url}" stands for the value of the system + * property <code>gnu.classpath.home.url</code>. + * + * <p>The task of configuring the framework is then delegated to + * {@link #readConfiguration(java.io.InputStream)}, which will + * notify registered listeners after having read the properties. + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure, or if the caller is + * not granted the permission to read the configuration + * file. + * + * @throws IOException if there is a problem reading in the + * configuration file. + */ + public synchronized void readConfiguration() + throws IOException, SecurityException + { + String path; + InputStream inputStream; + + path = System.getProperty("java.util.logging.config.file"); + if ((path == null) || (path.length() == 0)) + { + String url = (System.getProperty("gnu.classpath.home.url") + + "/logging.properties"); + try + { + inputStream = new URL(url).openStream(); + } + catch (Exception e) + { + inputStream=null; + } + + // If no config file could be found use a default configuration. + if(inputStream == null) + { + String defaultConfig = "handlers = java.util.logging.ConsoleHandler \n" + + ".level=INFO \n"; + inputStream = new ByteArrayInputStream(defaultConfig.getBytes()); + } + } + else + inputStream = new java.io.FileInputStream(path); + + try + { + readConfiguration(inputStream); + } + finally + { + // Close the stream in order to save + // resources such as file descriptors. + inputStream.close(); + } + } + + public synchronized void readConfiguration(InputStream inputStream) + throws IOException, SecurityException + { + Properties newProperties; + Enumeration keys; + + checkAccess(); + newProperties = new Properties(); + newProperties.load(inputStream); + reset(); + this.properties = newProperties; + keys = newProperties.propertyNames(); + + while (keys.hasMoreElements()) + { + String key = ((String) keys.nextElement()).trim(); + String value = newProperties.getProperty(key); + + if (value == null) + continue; + + value = value.trim(); + + if ("handlers".equals(key)) + { + // In Java 5 and earlier this was specified to be + // whitespace-separated, but in reality it also accepted + // commas (tomcat relied on this), and in Java 6 the + // documentation was updated to fit the implementation. + StringTokenizer tokenizer = new StringTokenizer(value, + " \t\n\r\f,"); + while (tokenizer.hasMoreTokens()) + { + String handlerName = tokenizer.nextToken(); + Handler handler = (Handler) + createInstance(handlerName, Handler.class, key); + // Tomcat also relies on the implementation ignoring + // items in 'handlers' which are not class names. + if (handler != null) + Logger.root.addHandler(handler); + } + } + + if (key.endsWith(".level")) + { + String loggerName = key.substring(0, key.length() - 6); + Logger logger = getLogger(loggerName); + + if (logger == null) + { + logger = Logger.getLogger(loggerName); + addLogger(logger); + } + Level level = null; + try + { + level = Level.parse(value); + } + catch (IllegalArgumentException e) + { + warn("bad level \'" + value + "\'", e); + } + if (level != null) + { + logger.setLevel(level); + } + continue; + } + } + + /* The API specification does not talk about the + * property name that is distributed with the + * PropertyChangeEvent. With test code, it could + * be determined that the Sun J2SE 1.4 reference + * implementation uses null for the property name. + */ + pcs.firePropertyChange(null, null, null); + } + + /** + * Returns the value of a configuration property as a String. + */ + public synchronized String getProperty(String name) + { + if (properties != null) + return properties.getProperty(name); + else + return null; + } + + /** + * Returns the value of a configuration property as an integer. + * This function is a helper used by the Classpath implementation + * of java.util.logging, it is <em>not</em> specified in the + * logging API. + * + * @param name the name of the configuration property. + * + * @param defaultValue the value that will be returned if the + * property is not defined, or if its value is not an integer + * number. + */ + static int getIntProperty(String name, int defaultValue) + { + try + { + return Integer.parseInt(getLogManager().getProperty(name)); + } + catch (Exception ex) + { + return defaultValue; + } + } + + /** + * Returns the value of a configuration property as an integer, + * provided it is inside the acceptable range. + * This function is a helper used by the Classpath implementation + * of java.util.logging, it is <em>not</em> specified in the + * logging API. + * + * @param name the name of the configuration property. + * + * @param minValue the lowest acceptable value. + * + * @param maxValue the highest acceptable value. + * + * @param defaultValue the value that will be returned if the + * property is not defined, or if its value is not an integer + * number, or if it is less than the minimum value, + * or if it is greater than the maximum value. + */ + static int getIntPropertyClamped(String name, int defaultValue, + int minValue, int maxValue) + { + int val = getIntProperty(name, defaultValue); + if ((val < minValue) || (val > maxValue)) + val = defaultValue; + return val; + } + + /** + * Returns the value of a configuration property as a boolean. + * This function is a helper used by the Classpath implementation + * of java.util.logging, it is <em>not</em> specified in the + * logging API. + * + * @param name the name of the configuration property. + * + * @param defaultValue the value that will be returned if the + * property is not defined, or if its value is neither + * <code>"true"</code> nor <code>"false"</code>. + */ + static boolean getBooleanProperty(String name, boolean defaultValue) + { + try + { + return (Boolean.valueOf(getLogManager().getProperty(name))).booleanValue(); + } + catch (Exception ex) + { + return defaultValue; + } + } + + /** + * Returns the value of a configuration property as a Level. + * This function is a helper used by the Classpath implementation + * of java.util.logging, it is <em>not</em> specified in the + * logging API. + * + * @param propertyName the name of the configuration property. + * + * @param defaultValue the value that will be returned if the + * property is not defined, or if + * {@link Level#parse(java.lang.String)} does not like + * the property value. + */ + static Level getLevelProperty(String propertyName, Level defaultValue) + { + try + { + String value = getLogManager().getProperty(propertyName); + if (value != null) + return Level.parse(getLogManager().getProperty(propertyName)); + else + return defaultValue; + } + catch (Exception ex) + { + return defaultValue; + } + } + + /** + * Returns the value of a configuration property as a Class. + * This function is a helper used by the Classpath implementation + * of java.util.logging, it is <em>not</em> specified in the + * logging API. + * + * @param propertyName the name of the configuration property. + * + * @param defaultValue the value that will be returned if the + * property is not defined, or if it does not specify + * the name of a loadable class. + */ + static final Class getClassProperty(String propertyName, Class defaultValue) + { + String propertyValue = logManager.getProperty(propertyName); + + if (propertyValue != null) + try + { + return locateClass(propertyValue); + } + catch (ClassNotFoundException e) + { + warn(propertyName + " = " + propertyValue, e); + } + + return defaultValue; + } + + static final Object getInstanceProperty(String propertyName, Class ofClass, + Class defaultClass) + { + Class klass = getClassProperty(propertyName, defaultClass); + if (klass == null) + return null; + + try + { + Object obj = klass.newInstance(); + if (ofClass.isInstance(obj)) + return obj; + } + catch (InstantiationException e) + { + warn(propertyName + " = " + klass.getName(), e); + } + catch (IllegalAccessException e) + { + warn(propertyName + " = " + klass.getName(), e); + } + + if (defaultClass == null) + return null; + + try + { + return defaultClass.newInstance(); + } + catch (java.lang.InstantiationException ex) + { + throw new RuntimeException(ex.getMessage()); + } + catch (java.lang.IllegalAccessException ex) + { + throw new RuntimeException(ex.getMessage()); + } + } + + /** + * An instance of <code>LoggingPermission("control")</code> + * that is shared between calls to <code>checkAccess()</code>. + */ + private static final LoggingPermission controlPermission = new LoggingPermission("control", + null); + + /** + * Checks whether the current security context allows changing + * the configuration of the logging framework. For the security + * context to be trusted, it has to be granted + * a LoggingPermission("control"). + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + */ + public void checkAccess() throws SecurityException + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(controlPermission); + } + + /** + * Creates a new instance of a class specified by name and verifies + * that it is an instance (or subclass of) a given type. + * + * @param className the name of the class of which a new instance + * should be created. + * + * @param type the object created must be an instance of + * <code>type</code> or any subclass of <code>type</code> + * + * @param property the system property to reference in error + * messages + * + * @return the new instance, or <code>null</code> if + * <code>className</code> is <code>null</code>, if no class + * with that name could be found, if there was an error + * loading that class, or if the constructor of the class + * has thrown an exception. + */ + private static final Object createInstance(String className, Class type, + String property) + { + Class klass = null; + + if ((className == null) || (className.length() == 0)) + return null; + + try + { + klass = locateClass(className); + if (type.isAssignableFrom(klass)) + return klass.newInstance(); + warn(property, className, "not an instance of " + type.getName()); + } + catch (ClassNotFoundException e) + { + warn(property, className, "class not found", e); + } + catch (IllegalAccessException e) + { + warn(property, className, "illegal access", e); + } + catch (InstantiationException e) + { + warn(property, className, e); + } + catch (java.lang.LinkageError e) + { + warn(property, className, "linkage error", e); + } + + return null; + } + + private static final void warn(String property, String klass, Throwable t) + { + warn(property, klass, null, t); + } + + private static final void warn(String property, String klass, String msg) + { + warn(property, klass, msg, null); + } + + private static final void warn(String property, String klass, String msg, + Throwable t) + { + warn("error instantiating '" + klass + "' referenced by " + property + + (msg == null ? "" : ", " + msg), t); + } + + /** + * All debug warnings go through this method. + */ + + private static final void warn(String msg, Throwable t) + { + System.err.println("WARNING: " + msg); + if (t != null) + t.printStackTrace(System.err); + } + + /** + * Locates a class by first checking the system class loader and + * then checking the context class loader. + * + * @param name the fully qualified name of the Class to locate + * @return Class the located Class + */ + + private static Class locateClass(String name) throws ClassNotFoundException + { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try + { + return Class.forName(name, true, loader); + } + catch (ClassNotFoundException e) + { + loader = ClassLoader.getSystemClassLoader(); + return Class.forName(name, true, loader); + } + } + + /** + * Return the logging bean. There is a single logging bean per + * VM instance. + * @since 1.5 + */ + public static synchronized LoggingMXBean getLoggingMXBean() + { + if (loggingBean == null) + { + loggingBean = new LoggingMXBean() + { + public String getLoggerLevel(String logger) + { + LogManager mgr = getLogManager(); + Logger l = mgr.getLogger(logger); + if (l == null) + return null; + Level lev = l.getLevel(); + if (lev == null) + return ""; + return lev.getName(); + } + + public List getLoggerNames() + { + LogManager mgr = getLogManager(); + // This is inefficient, but perhaps better for maintenance. + return Collections.list(mgr.getLoggerNames()); + } + + public String getParentLoggerName(String logger) + { + LogManager mgr = getLogManager(); + Logger l = mgr.getLogger(logger); + if (l == null) + return null; + l = l.getParent(); + if (l == null) + return ""; + return l.getName(); + } + + public void setLoggerLevel(String logger, String level) + { + LogManager mgr = getLogManager(); + Logger l = mgr.getLogger(logger); + if (l == null) + throw new IllegalArgumentException("no logger named " + logger); + Level newLevel; + if (level == null) + newLevel = null; + else + newLevel = Level.parse(level); + l.setLevel(newLevel); + } + }; + } + return loggingBean; + } +} diff --git a/libjava/classpath/java/util/logging/LogRecord.java b/libjava/classpath/java/util/logging/LogRecord.java new file mode 100644 index 000000000..ee99ee69a --- /dev/null +++ b/libjava/classpath/java/util/logging/LogRecord.java @@ -0,0 +1,672 @@ +/* LogRecord.java -- + A class for the state associated with individual logging events + Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import java.util.ResourceBundle; + + +/** + * A <code>LogRecord</code> contains the state for an individual + * event to be logged. + * + * <p>As soon as a LogRecord instance has been handed over to the + * logging framework, applications should not manipulate it anymore. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class LogRecord + implements java.io.Serializable +{ + /** + * The severity level of this <code>LogRecord</code>. + */ + private Level level; + + + /** + * The sequence number of this <code>LogRecord</code>. + */ + private long sequenceNumber; + + + /** + * The name of the class that issued the logging request, or + * <code>null</code> if this information could not be obtained. + */ + private String sourceClassName; + + + /** + * The name of the method that issued the logging request, or + * <code>null</code> if this information could not be obtained. + */ + private String sourceMethodName; + + + /** + * The message for this <code>LogRecord</code> before + * any localization or formatting. + */ + private String message; + + + /** + * An identifier for the thread in which this <code>LogRecord</code> + * was created. The identifier is not necessarily related to any + * thread identifiers used by the operating system. + */ + private int threadID; + + + /** + * The time when this <code>LogRecord</code> was created, + * in milliseconds since the beginning of January 1, 1970. + */ + private long millis; + + + /** + * The Throwable associated with this <code>LogRecord</code>, or + * <code>null</code> if the logged event is not related to an + * exception or error. + */ + private Throwable thrown; + + + /** + * The name of the logger where this <code>LogRecord</code> has + * originated, or <code>null</code> if this <code>LogRecord</code> + * does not originate from a <code>Logger</code>. + */ + private String loggerName; + + + /** + * The name of the resource bundle used for localizing log messages, + * or <code>null</code> if no bundle has been specified. + */ + private String resourceBundleName; + + private transient Object[] parameters; + + private transient ResourceBundle bundle; + + + /** + * Constructs a <code>LogRecord</code> given a severity level and + * an unlocalized message text. In addition, the sequence number, + * creation time (as returned by <code>getMillis()</code>) and + * thread ID are assigned. All other properties are set to + * <code>null</code>. + * + * @param level the severity level, for example <code>Level.WARNING</code>. + * + * @param message the message text (which will be used as key + * for looking up the localized message text + * if a resource bundle has been associated). + */ + public LogRecord(Level level, String message) + { + this.level = level; + this.message = message; + this.millis = System.currentTimeMillis(); + + /* A subclass of java.lang.Thread could override hashCode(), + * in which case the result would not be guaranteed anymore + * to be unique among all threads. While System.identityHashCode + * is not necessarily unique either, it at least cannot be + * overridden by user code. However, is might be a good idea + * to use something better for generating thread IDs. + */ + this.threadID = System.identityHashCode(Thread.currentThread()); + + sequenceNumber = allocateSeqNum(); + } + + + /** + * Determined with the serialver tool of the Sun J2SE 1.4. + */ + static final long serialVersionUID = 5372048053134512534L; + + private void readObject(java.io.ObjectInputStream in) + throws java.io.IOException, java.lang.ClassNotFoundException + { + in.defaultReadObject(); + + /* We assume that future versions will be downwards compatible, + * so we can ignore the versions. + */ + byte majorVersion = in.readByte(); + byte minorVersion = in.readByte(); + + int numParams = in.readInt(); + if (numParams >= 0) + { + parameters = new Object[numParams]; + for (int i = 0; i < numParams; i++) + parameters[i] = in.readObject(); + } + } + + + /** + * @serialData The default fields, followed by a major byte version + * number, followed by a minor byte version number, followed by + * information about the log record parameters. If + * <code>parameters</code> is <code>null</code>, the integer -1 is + * written, otherwise the length of the <code>parameters</code> + * array (which can be zero), followed by the result of calling + * {@link Object#toString() toString()} on the parameter (or + * <code>null</code> if the parameter is <code>null</code>). + * + * <p><strong>Specification Note:</strong> The Javadoc for the + * Sun reference implementation does not specify the version + * number. FIXME: Reverse-engineer the JDK and file a bug + * report with Sun, asking for amendment of the specification. + */ + private void writeObject(java.io.ObjectOutputStream out) + throws java.io.IOException + { + out.defaultWriteObject(); + + /* Major, minor version number: The Javadoc for J2SE1.4 does not + * specify the values. + */ + out.writeByte(0); + out.writeByte(0); + + if (parameters == null) + out.writeInt(-1); + else + { + out.writeInt(parameters.length); + for (int i = 0; i < parameters.length; i++) + { + if (parameters[i] == null) + out.writeObject(null); + else + out.writeObject(parameters[i].toString()); + } + } + } + + + /** + * Returns the name of the logger where this <code>LogRecord</code> + * has originated. + * + * @return the name of the source {@link Logger}, or + * <code>null</code> if this <code>LogRecord</code> + * does not originate from a <code>Logger</code>. + */ + public String getLoggerName() + { + return loggerName; + } + + + /** + * Sets the name of the logger where this <code>LogRecord</code> + * has originated. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param name the name of the source logger, or <code>null</code> to + * indicate that this <code>LogRecord</code> does not + * originate from a <code>Logger</code>. + */ + public void setLoggerName(String name) + { + loggerName = name; + } + + + /** + * Returns the resource bundle that is used when the message + * of this <code>LogRecord</code> needs to be localized. + * + * @return the resource bundle used for localization, + * or <code>null</code> if this message does not need + * to be localized. + */ + public ResourceBundle getResourceBundle() + { + return bundle; + } + + + /** + * Sets the resource bundle that is used when the message + * of this <code>LogRecord</code> needs to be localized. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param bundle the resource bundle to be used, or + * <code>null</code> to indicate that this + * message does not need to be localized. + */ + public void setResourceBundle(ResourceBundle bundle) + { + this.bundle = bundle; + + /* FIXME: Is there a way to infer the name + * of a resource bundle from a ResourceBundle object? + */ + this.resourceBundleName = null; + } + + + /** + * Returns the name of the resource bundle that is used when the + * message of this <code>LogRecord</code> needs to be localized. + * + * @return the name of the resource bundle used for localization, + * or <code>null</code> if this message does not need + * to be localized. + */ + public String getResourceBundleName() + { + return resourceBundleName; + } + + + /** + * Sets the name of the resource bundle that is used when the + * message of this <code>LogRecord</code> needs to be localized. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param name the name of the resource bundle to be used, or + * <code>null</code> to indicate that this message + * does not need to be localized. + */ + public void setResourceBundleName(String name) + { + resourceBundleName = name; + bundle = null; + + try + { + if (resourceBundleName != null) + bundle = ResourceBundle.getBundle(resourceBundleName); + } + catch (java.util.MissingResourceException _) + { + } + } + + + /** + * Returns the level of the LogRecord. + * + * <p>Applications should be aware of the possibility that the + * result is not necessarily one of the standard logging levels, + * since the logging framework allows to create custom subclasses + * of <code>java.util.logging.Level</code>. Therefore, filters + * should perform checks like <code>theRecord.getLevel().intValue() + * == Level.INFO.intValue()</code> instead of <code>theRecord.getLevel() + * == Level.INFO</code>. + */ + public Level getLevel() + { + return level; + } + + + /** + * Sets the severity level of this <code>LogRecord</code> to a new + * value. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param level the new severity level, for example + * <code>Level.WARNING</code>. + */ + public void setLevel(Level level) + { + this.level = level; + } + + + /** + * The last used sequence number for any LogRecord. + */ + private static long lastSeqNum; + + + /** + * Allocates a sequence number for a new LogRecord. This class + * method is only called by the LogRecord constructor. + */ + private static synchronized long allocateSeqNum() + { + lastSeqNum += 1; + return lastSeqNum; + } + + + /** + * Returns the sequence number of this <code>LogRecord</code>. + */ + public long getSequenceNumber() + { + return sequenceNumber; + } + + + /** + * Sets the sequence number of this <code>LogRecord</code> to a new + * value. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param seqNum the new sequence number. + */ + public void setSequenceNumber(long seqNum) + { + this.sequenceNumber = seqNum; + } + + + /** + * Returns the name of the class where the event being logged + * has had its origin. This information can be passed as + * parameter to some logging calls, and in certain cases, the + * logging framework tries to determine an approximation + * (which may or may not be accurate). + * + * @return the name of the class that issued the logging request, + * or <code>null</code> if this information could not + * be obtained. + */ + public String getSourceClassName() + { + if (sourceClassName != null) + return sourceClassName; + + /* FIXME: Should infer this information from the call stack. */ + return null; + } + + + /** + * Sets the name of the class where the event being logged + * has had its origin. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param sourceClassName the name of the class that issued the + * logging request, or <code>null</code> to indicate that + * this information could not be obtained. + */ + public void setSourceClassName(String sourceClassName) + { + this.sourceClassName = sourceClassName; + } + + + /** + * Returns the name of the method where the event being logged + * has had its origin. This information can be passed as + * parameter to some logging calls, and in certain cases, the + * logging framework tries to determine an approximation + * (which may or may not be accurate). + * + * @return the name of the method that issued the logging request, + * or <code>null</code> if this information could not + * be obtained. + */ + public String getSourceMethodName() + { + if (sourceMethodName != null) + return sourceMethodName; + + /* FIXME: Should infer this information from the call stack. */ + return null; + } + + + /** + * Sets the name of the method where the event being logged + * has had its origin. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param sourceMethodName the name of the method that issued the + * logging request, or <code>null</code> to indicate that + * this information could not be obtained. + */ + public void setSourceMethodName(String sourceMethodName) + { + this.sourceMethodName = sourceMethodName; + } + + + /** + * Returns the message for this <code>LogRecord</code> before + * any localization or parameter substitution. + * + * <p>A {@link Logger} will try to localize the message + * if a resource bundle has been associated with this + * <code>LogRecord</code>. In this case, the logger will call + * <code>getMessage()</code> and use the result as the key + * for looking up the localized message in the bundle. + * If no bundle has been associated, or if the result of + * <code>getMessage()</code> is not a valid key in the + * bundle, the logger will use the raw message text as + * returned by this method. + * + * @return the message text, or <code>null</code> if there + * is no message text. + */ + public String getMessage() + { + return message; + } + + + /** + * Sets the message for this <code>LogRecord</code>. + * + * <p>A <code>Logger</code> will try to localize the message + * if a resource bundle has been associated with this + * <code>LogRecord</code>. In this case, the logger will call + * <code>getMessage()</code> and use the result as the key + * for looking up the localized message in the bundle. + * If no bundle has been associated, or if the result of + * <code>getMessage()</code> is not a valid key in the + * bundle, the logger will use the raw message text as + * returned by this method. + * + * <p>It is possible to set the message to either an empty String or + * <code>null</code>, although this does not make the the message + * very helpful to human users. + * + * @param message the message text (which will be used as key + * for looking up the localized message text + * if a resource bundle has been associated). + */ + public void setMessage(String message) + { + this.message = message; + } + + + /** + * Returns the parameters to the log message. + * + * @return the parameters to the message, or <code>null</code> if + * the message has no parameters. + */ + public Object[] getParameters() + { + return parameters; + } + + + /** + * Sets the parameters to the log message. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param parameters the parameters to the message, or <code>null</code> + * to indicate that the message has no parameters. + */ + public void setParameters(Object[] parameters) + { + this.parameters = parameters; + } + + + /** + * Returns an identifier for the thread in which this + * <code>LogRecord</code> was created. The identifier is not + * necessarily related to any thread identifiers used by the + * operating system. + * + * @return an identifier for the source thread. + */ + public int getThreadID() + { + return threadID; + } + + + /** + * Sets the identifier indicating in which thread this + * <code>LogRecord</code> was created. The identifier is not + * necessarily related to any thread identifiers used by the + * operating system. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param threadID the identifier for the source thread. + */ + public void setThreadID(int threadID) + { + this.threadID = threadID; + } + + + /** + * Returns the time when this <code>LogRecord</code> was created. + * + * @return the time of creation in milliseconds since the beginning + * of January 1, 1970. + */ + public long getMillis() + { + return millis; + } + + + /** + * Sets the time when this <code>LogRecord</code> was created. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param millis the time of creation in milliseconds since the + * beginning of January 1, 1970. + */ + public void setMillis(long millis) + { + this.millis = millis; + } + + + /** + * Returns the Throwable associated with this <code>LogRecord</code>, + * or <code>null</code> if the logged event is not related to an exception + * or error. + */ + public Throwable getThrown() + { + return thrown; + } + + + /** + * Associates this <code>LogRecord</code> with an exception or error. + * + * <p>As soon as a <code>LogRecord</code> has been handed over + * to the logging framework, applications should not modify it + * anymore. Therefore, this method should only be called on + * freshly constructed LogRecords. + * + * @param thrown the exception or error to associate with, or + * <code>null</code> if this <code>LogRecord</code> + * should be made unrelated to an exception or error. + */ + public void setThrown(Throwable thrown) + { + this.thrown = thrown; + } +} diff --git a/libjava/classpath/java/util/logging/Logger.java b/libjava/classpath/java/util/logging/Logger.java new file mode 100644 index 000000000..c55e133e5 --- /dev/null +++ b/libjava/classpath/java/util/logging/Logger.java @@ -0,0 +1,1193 @@ +/* Logger.java -- a class for logging messages + Copyright (C) 2002, 2004, 2006, 2007 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import gnu.java.lang.CPStringBuilder; + +import java.util.List; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * A Logger is used for logging information about events. Usually, there is a + * seprate logger for each subsystem or component, although there is a shared + * instance for components that make only occasional use of the logging + * framework. + * <p> + * It is common to name a logger after the name of a corresponding Java package. + * Loggers are organized into a hierarchical namespace; for example, the logger + * <code>"org.gnu.foo"</code> is the <em>parent</em> of logger + * <code>"org.gnu.foo.bar"</code>. + * <p> + * A logger for a named subsystem can be obtained through {@link + * java.util.logging.Logger#getLogger(java.lang.String)}. However, only code + * which has been granted the permission to control the logging infrastructure + * will be allowed to customize that logger. Untrusted code can obtain a + * private, anonymous logger through {@link #getAnonymousLogger()} if it wants + * to perform any modifications to the logger. + * <p> + * FIXME: Write more documentation. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class Logger +{ + static final Logger root = new Logger("", null); + + /** + * A logger provided to applications that make only occasional use of the + * logging framework, typically early prototypes. Serious products are + * supposed to create and use their own Loggers, so they can be controlled + * individually. + */ + public static final Logger global; + + /** + * Use to lock methods on this class instead of calling synchronize on methods + * to avoid deadlocks. Yeah, no kidding, we got them :) + */ + private static final Object[] lock = new Object[0]; + + static + { + // Our class might be initialized from an unprivileged context + global = (Logger) AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + return getLogger("global"); + } + }); + } + + /** + * The name of the Logger, or <code>null</code> if the logger is anonymous. + * <p> + * A previous version of the GNU Classpath implementation granted untrusted + * code the permission to control any logger whose name was null. However, + * test code revealed that the Sun J2SE 1.4 reference implementation enforces + * the security control for any logger that was not created through + * getAnonymousLogger, even if it has a null name. Therefore, a separate flag + * {@link Logger#anonymous} was introduced. + */ + private final String name; + + /** + * The name of the resource bundle used for localization. + * <p> + * This variable cannot be declared as <code>final</code> because its value + * can change as a result of calling getLogger(String,String). + */ + private String resourceBundleName; + + /** + * The resource bundle used for localization. + * <p> + * This variable cannot be declared as <code>final</code> because its value + * can change as a result of calling getLogger(String,String). + */ + private ResourceBundle resourceBundle; + + private Filter filter; + + private final List handlerList = new java.util.ArrayList(4); + + private Handler[] handlers = new Handler[0]; + + /** + * Indicates whether or not this logger is anonymous. While a + * LoggingPermission is required for any modifications to a normal logger, + * untrusted code can obtain an anonymous logger and modify it according to + * its needs. + * <p> + * A previous version of the GNU Classpath implementation granted access to + * every logger whose name was null. However, test code revealed that the Sun + * J2SE 1.4 reference implementation enforces the security control for any + * logger that was not created through getAnonymousLogger, even if it has a + * null name. + */ + private boolean anonymous; + + private boolean useParentHandlers; + + private Level level; + + private Logger parent; + + /** + * Constructs a Logger for a subsystem. Most applications do not need to + * create new Loggers explicitly; instead, they should call the static factory + * methods {@link #getLogger(java.lang.String,java.lang.String) getLogger} + * (with ResourceBundle for localization) or + * {@link #getLogger(java.lang.String) getLogger} (without ResourceBundle), + * respectively. + * + * @param name the name for the logger, for example "java.awt" or + * "com.foo.bar". The name should be based on the name of the + * package issuing log records and consist of dot-separated Java + * identifiers. + * @param resourceBundleName the name of a resource bundle for localizing + * messages, or <code>null</code> to indicate that messages do + * not need to be localized. + * @throws java.util.MissingResourceException if + * <code>resourceBundleName</code> is not <code>null</code> + * and no such bundle could be located. + */ + protected Logger(String name, String resourceBundleName) + throws MissingResourceException + { + this.name = name; + this.resourceBundleName = resourceBundleName; + + if (resourceBundleName == null) + resourceBundle = null; + else + resourceBundle = ResourceBundle.getBundle(resourceBundleName); + + level = null; + + /* + * This is null when the root logger is being constructed, and the root + * logger afterwards. + */ + parent = root; + + useParentHandlers = (parent != null); + } + + /** + * Finds a registered logger for a subsystem, or creates one in case no logger + * has been registered yet. + * + * @param name the name for the logger, for example "java.awt" or + * "com.foo.bar". The name should be based on the name of the + * package issuing log records and consist of dot-separated Java + * identifiers. + * @throws IllegalArgumentException if a logger for the subsystem identified + * by <code>name</code> has already been created, but uses a a + * resource bundle for localizing messages. + * @throws NullPointerException if <code>name</code> is <code>null</code>. + * @return a logger for the subsystem specified by <code>name</code> that + * does not localize messages. + */ + public static Logger getLogger(String name) + { + return getLogger(name, null); + } + + /** + * Finds a registered logger for a subsystem, or creates one in case no logger + * has been registered yet. + * <p> + * If a logger with the specified name has already been registered, the + * behavior depends on the resource bundle that is currently associated with + * the existing logger. + * <ul> + * <li>If the existing logger uses the same resource bundle as specified by + * <code>resourceBundleName</code>, the existing logger is returned.</li> + * <li>If the existing logger currently does not localize messages, the + * existing logger is modified to use the bundle specified by + * <code>resourceBundleName</code>. The existing logger is then returned. + * Therefore, all subsystems currently using this logger will produce + * localized messages from now on.</li> + * <li>If the existing logger already has an associated resource bundle, but + * a different one than specified by <code>resourceBundleName</code>, an + * <code>IllegalArgumentException</code> is thrown.</li> + * </ul> + * + * @param name the name for the logger, for example "java.awt" or + * "org.gnu.foo". The name should be based on the name of the + * package issuing log records and consist of dot-separated Java + * identifiers. + * @param resourceBundleName the name of a resource bundle for localizing + * messages, or <code>null</code> to indicate that messages do + * not need to be localized. + * @return a logger for the subsystem specified by <code>name</code>. + * @throws java.util.MissingResourceException if + * <code>resourceBundleName</code> is not <code>null</code> + * and no such bundle could be located. + * @throws IllegalArgumentException if a logger for the subsystem identified + * by <code>name</code> has already been created, but uses a + * different resource bundle for localizing messages. + * @throws NullPointerException if <code>name</code> is <code>null</code>. + */ + public static Logger getLogger(String name, String resourceBundleName) + { + LogManager lm = LogManager.getLogManager(); + Logger result; + + if (name == null) + throw new NullPointerException(); + + /* + * Without synchronized(lm), it could happen that another thread would + * create a logger between our calls to getLogger and addLogger. While + * addLogger would indicate this by returning false, we could not be sure + * that this other logger was still existing when we called getLogger a + * second time in order to retrieve it -- note that LogManager is only + * allowed to keep weak references to registered loggers, so Loggers can be + * garbage collected at any time in general, and between our call to + * addLogger and our second call go getLogger in particular. Of course, we + * assume here that LogManager.addLogger etc. are synchronizing on the + * global LogManager object. There is a comment in the implementation of + * LogManager.addLogger referring to this comment here, so that any change + * in the synchronization of LogManager will be reflected here. + */ + synchronized (lock) + { + synchronized (lm) + { + result = lm.getLogger(name); + if (result == null) + { + boolean couldBeAdded; + + result = new Logger(name, resourceBundleName); + couldBeAdded = lm.addLogger(result); + if (! couldBeAdded) + throw new IllegalStateException("cannot register new logger"); + } + else + { + /* + * The logger already exists. Make sure it uses the same + * resource bundle for localizing messages. + */ + String existingBundleName = result.getResourceBundleName(); + + /* + * The Sun J2SE 1.4 reference implementation will return the + * registered logger object, even if it does not have a resource + * bundle associated with it. However, it seems to change the + * resourceBundle of the registered logger to the bundle whose + * name was passed to getLogger. + */ + if ((existingBundleName == null) && + (resourceBundleName != null)) + { + /* + * If ResourceBundle.getBundle throws an exception, the + * existing logger will be unchanged. This would be + * different if the assignment to resourceBundleName came + * first. + */ + result.resourceBundle = + ResourceBundle.getBundle(resourceBundleName); + + result.resourceBundleName = resourceBundleName; + return result; + } + + if ((existingBundleName != resourceBundleName) + && ((existingBundleName == null) + || !existingBundleName.equals(resourceBundleName))) + { + throw new IllegalArgumentException(); + } + } + } + } + + return result; + } + + /** + * Creates a new, unnamed logger. Unnamed loggers are not registered in the + * namespace of the LogManager, and no special security permission is required + * for changing their state. Therefore, untrusted applets are able to modify + * their private logger instance obtained through this method. + * <p> + * The parent of the newly created logger will the the root logger, from which + * the level threshold and the handlers are inherited. + */ + public static Logger getAnonymousLogger() + { + return getAnonymousLogger(null); + } + + /** + * Creates a new, unnamed logger. Unnamed loggers are not registered in the + * namespace of the LogManager, and no special security permission is required + * for changing their state. Therefore, untrusted applets are able to modify + * their private logger instance obtained through this method. + * <p> + * The parent of the newly created logger will the the root logger, from which + * the level threshold and the handlers are inherited. + * + * @param resourceBundleName the name of a resource bundle for localizing + * messages, or <code>null</code> to indicate that messages do + * not need to be localized. + * @throws java.util.MissingResourceException if + * <code>resourceBundleName</code> is not <code>null</code> + * and no such bundle could be located. + */ + public static Logger getAnonymousLogger(String resourceBundleName) + throws MissingResourceException + { + Logger result; + + result = new Logger(null, resourceBundleName); + result.anonymous = true; + return result; + } + + /** + * Returns the name of the resource bundle that is being used for localizing + * messages. + * + * @return the name of the resource bundle used for localizing messages, or + * <code>null</code> if the parent's resource bundle is used for + * this purpose. + */ + public String getResourceBundleName() + { + synchronized (lock) + { + return resourceBundleName; + } + } + + /** + * Returns the resource bundle that is being used for localizing messages. + * + * @return the resource bundle used for localizing messages, or + * <code>null</code> if the parent's resource bundle is used for + * this purpose. + */ + public ResourceBundle getResourceBundle() + { + synchronized (lock) + { + return resourceBundle; + } + } + + /** + * Returns the severity level threshold for this <code>Handler</code>. All + * log records with a lower severity level will be discarded; a log record of + * the same or a higher level will be published unless an installed + * <code>Filter</code> decides to discard it. + * + * @return the severity level below which all log messages will be discarded, + * or <code>null</code> if the logger inherits the threshold from + * its parent. + */ + public Level getLevel() + { + synchronized (lock) + { + return level; + } + } + + /** + * Returns whether or not a message of the specified level would be logged by + * this logger. + * + * @throws NullPointerException if <code>level</code> is <code>null</code>. + */ + public boolean isLoggable(Level level) + { + synchronized (lock) + { + if (this.level != null) + return this.level.intValue() <= level.intValue(); + + if (parent != null) + return parent.isLoggable(level); + else + return false; + } + } + + /** + * Sets the severity level threshold for this <code>Handler</code>. All log + * records with a lower severity level will be discarded immediately. A log + * record of the same or a higher level will be published unless an installed + * <code>Filter</code> decides to discard it. + * + * @param level the severity level below which all log messages will be + * discarded, or <code>null</code> to indicate that the logger + * should inherit the threshold from its parent. + * @throws SecurityException if this logger is not anonymous, a security + * manager exists, and the caller is not granted the permission to + * control the logging infrastructure by having + * LoggingPermission("control"). Untrusted code can obtain an + * anonymous logger through the static factory method + * {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}. + */ + public void setLevel(Level level) + { + synchronized (lock) + { + /* + * An application is allowed to control an anonymous logger without + * having the permission to control the logging infrastructure. + */ + if (! anonymous) + LogManager.getLogManager().checkAccess(); + + this.level = level; + } + } + + public Filter getFilter() + { + synchronized (lock) + { + return filter; + } + } + + /** + * @throws SecurityException if this logger is not anonymous, a security + * manager exists, and the caller is not granted the permission to + * control the logging infrastructure by having + * LoggingPermission("control"). Untrusted code can obtain an + * anonymous logger through the static factory method + * {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}. + */ + public void setFilter(Filter filter) throws SecurityException + { + synchronized (lock) + { + /* + * An application is allowed to control an anonymous logger without + * having the permission to control the logging infrastructure. + */ + if (! anonymous) + LogManager.getLogManager().checkAccess(); + + this.filter = filter; + } + } + + /** + * Returns the name of this logger. + * + * @return the name of this logger, or <code>null</code> if the logger is + * anonymous. + */ + public String getName() + { + /* + * Note that the name of a logger cannot be changed during its lifetime, so + * no synchronization is needed. + */ + return name; + } + + /** + * Passes a record to registered handlers, provided the record is considered + * as loggable both by {@link #isLoggable(Level)} and a possibly installed + * custom {@link #setFilter(Filter) filter}. + * <p> + * If the logger has been configured to use parent handlers, the record will + * be forwarded to the parent of this logger in addition to being processed by + * the handlers registered with this logger. + * <p> + * The other logging methods in this class are convenience methods that merely + * create a new LogRecord and pass it to this method. Therefore, subclasses + * usually just need to override this single method for customizing the + * logging behavior. + * + * @param record the log record to be inspected and possibly forwarded. + */ + public void log(LogRecord record) + { + synchronized (lock) + { + if (!isLoggable(record.getLevel())) + return; + + if ((filter != null) && ! filter.isLoggable(record)) + return; + + /* + * If no logger name has been set for the log record, use the name of + * this logger. + */ + if (record.getLoggerName() == null) + record.setLoggerName(name); + + /* + * Avoid that some other thread is changing the logger hierarchy while + * we are traversing it. + */ + synchronized (LogManager.getLogManager()) + { + Logger curLogger = this; + + do + { + /* + * The Sun J2SE 1.4 reference implementation seems to call the + * filter only for the logger whose log method is called, never + * for any of its parents. Also, parent loggers publish log + * record whatever their level might be. This is pretty weird, + * but GNU Classpath tries to be as compatible as possible to + * the reference implementation. + */ + for (int i = 0; i < curLogger.handlers.length; i++) + curLogger.handlers[i].publish(record); + + if (curLogger.getUseParentHandlers() == false) + break; + + curLogger = curLogger.getParent(); + } + while (parent != null); + } + } + } + + public void log(Level level, String message) + { + if (isLoggable(level)) + log(level, message, (Object[]) null); + } + + public void log(Level level, String message, Object param) + { + synchronized (lock) + { + if (isLoggable(level)) + { + StackTraceElement caller = getCallerStackFrame(); + logp(level, caller != null ? caller.getClassName() : "<unknown>", + caller != null ? caller.getMethodName() : "<unknown>", + message, param); + } + } + } + + public void log(Level level, String message, Object[] params) + { + synchronized (lock) + { + if (isLoggable(level)) + { + StackTraceElement caller = getCallerStackFrame(); + logp(level, caller != null ? caller.getClassName() : "<unknown>", + caller != null ? caller.getMethodName() : "<unknown>", + message, params); + + } + } + } + + public void log(Level level, String message, Throwable thrown) + { + synchronized (lock) + { + if (isLoggable(level)) + { + StackTraceElement caller = getCallerStackFrame(); + logp(level, caller != null ? caller.getClassName() : "<unknown>", + caller != null ? caller.getMethodName() : "<unknown>", + message, thrown); + } + } + } + + public void logp(Level level, String sourceClass, String sourceMethod, + String message) + { + synchronized (lock) + { + logp(level, sourceClass, sourceMethod, message, (Object[]) null); + } + } + + public void logp(Level level, String sourceClass, String sourceMethod, + String message, Object param) + { + synchronized (lock) + { + logp(level, sourceClass, sourceMethod, message, new Object[] { param }); + } + + } + + private ResourceBundle findResourceBundle() + { + synchronized (lock) + { + if (resourceBundle != null) + return resourceBundle; + + if (parent != null) + return parent.findResourceBundle(); + + return null; + } + } + + private void logImpl(Level level, String sourceClass, String sourceMethod, + String message, Object[] params) + { + synchronized (lock) + { + LogRecord rec = new LogRecord(level, message); + + rec.setResourceBundle(findResourceBundle()); + rec.setSourceClassName(sourceClass); + rec.setSourceMethodName(sourceMethod); + rec.setParameters(params); + + log(rec); + } + } + + public void logp(Level level, String sourceClass, String sourceMethod, + String message, Object[] params) + { + synchronized (lock) + { + logImpl(level, sourceClass, sourceMethod, message, params); + } + } + + public void logp(Level level, String sourceClass, String sourceMethod, + String message, Throwable thrown) + { + synchronized (lock) + { + LogRecord rec = new LogRecord(level, message); + + rec.setResourceBundle(resourceBundle); + rec.setSourceClassName(sourceClass); + rec.setSourceMethodName(sourceMethod); + rec.setThrown(thrown); + + log(rec); + } + } + + public void logrb(Level level, String sourceClass, String sourceMethod, + String bundleName, String message) + { + synchronized (lock) + { + logrb(level, sourceClass, sourceMethod, bundleName, message, + (Object[]) null); + } + } + + public void logrb(Level level, String sourceClass, String sourceMethod, + String bundleName, String message, Object param) + { + synchronized (lock) + { + logrb(level, sourceClass, sourceMethod, bundleName, message, + new Object[] { param }); + } + } + + public void logrb(Level level, String sourceClass, String sourceMethod, + String bundleName, String message, Object[] params) + { + synchronized (lock) + { + LogRecord rec = new LogRecord(level, message); + + rec.setResourceBundleName(bundleName); + rec.setSourceClassName(sourceClass); + rec.setSourceMethodName(sourceMethod); + rec.setParameters(params); + + log(rec); + } + } + + public void logrb(Level level, String sourceClass, String sourceMethod, + String bundleName, String message, Throwable thrown) + { + synchronized (lock) + { + LogRecord rec = new LogRecord(level, message); + + rec.setResourceBundleName(bundleName); + rec.setSourceClassName(sourceClass); + rec.setSourceMethodName(sourceMethod); + rec.setThrown(thrown); + + log(rec); + } + } + + public void entering(String sourceClass, String sourceMethod) + { + synchronized (lock) + { + if (isLoggable(Level.FINER)) + logp(Level.FINER, sourceClass, sourceMethod, "ENTRY"); + } + } + + public void entering(String sourceClass, String sourceMethod, Object param) + { + synchronized (lock) + { + if (isLoggable(Level.FINER)) + logp(Level.FINER, sourceClass, sourceMethod, "ENTRY {0}", param); + } + } + + public void entering(String sourceClass, String sourceMethod, Object[] params) + { + synchronized (lock) + { + if (isLoggable(Level.FINER)) + { + CPStringBuilder buf = new CPStringBuilder(80); + buf.append("ENTRY"); + for (int i = 0; i < params.length; i++) + { + buf.append(" {"); + buf.append(i); + buf.append('}'); + } + + logp(Level.FINER, sourceClass, sourceMethod, buf.toString(), params); + } + } + } + + public void exiting(String sourceClass, String sourceMethod) + { + synchronized (lock) + { + if (isLoggable(Level.FINER)) + logp(Level.FINER, sourceClass, sourceMethod, "RETURN"); + } + } + + public void exiting(String sourceClass, String sourceMethod, Object result) + { + synchronized (lock) + { + if (isLoggable(Level.FINER)) + logp(Level.FINER, sourceClass, sourceMethod, "RETURN {0}", result); + } + } + + public void throwing(String sourceClass, String sourceMethod, Throwable thrown) + { + synchronized (lock) + { + if (isLoggable(Level.FINER)) + logp(Level.FINER, sourceClass, sourceMethod, "THROW", thrown); + } + } + + /** + * Logs a message with severity level SEVERE, indicating a serious failure + * that prevents normal program execution. Messages at this level should be + * understandable to an inexperienced, non-technical end user. Ideally, they + * explain in simple words what actions the user can take in order to resolve + * the problem. + * + * @see Level#SEVERE + * @param message the message text, also used as look-up key if the logger is + * localizing messages with a resource bundle. While it is possible + * to pass <code>null</code>, this is not recommended, since a + * logging message without text is unlikely to be helpful. + */ + public void severe(String message) + { + synchronized (lock) + { + if (isLoggable(Level.SEVERE)) + log(Level.SEVERE, message); + } + } + + /** + * Logs a message with severity level WARNING, indicating a potential problem + * that does not prevent normal program execution. Messages at this level + * should be understandable to an inexperienced, non-technical end user. + * Ideally, they explain in simple words what actions the user can take in + * order to resolve the problem. + * + * @see Level#WARNING + * @param message the message text, also used as look-up key if the logger is + * localizing messages with a resource bundle. While it is possible + * to pass <code>null</code>, this is not recommended, since a + * logging message without text is unlikely to be helpful. + */ + public void warning(String message) + { + synchronized (lock) + { + if (isLoggable(Level.WARNING)) + log(Level.WARNING, message); + } + } + + /** + * Logs a message with severity level INFO. {@link Level#INFO} is intended for + * purely informational messages that do not indicate error or warning + * situations. In the default logging configuration, INFO messages will be + * written to the system console. For this reason, the INFO level should be + * used only for messages that are important to end users and system + * administrators. Messages at this level should be understandable to an + * inexperienced, non-technical user. + * + * @param message the message text, also used as look-up key if the logger is + * localizing messages with a resource bundle. While it is possible + * to pass <code>null</code>, this is not recommended, since a + * logging message without text is unlikely to be helpful. + */ + public void info(String message) + { + synchronized (lock) + { + if (isLoggable(Level.INFO)) + log(Level.INFO, message); + } + } + + /** + * Logs a message with severity level CONFIG. {@link Level#CONFIG} is intended + * for static configuration messages, for example about the windowing + * environment, the operating system version, etc. + * + * @param message the message text, also used as look-up key if the logger is + * localizing messages with a resource bundle. While it is possible + * to pass <code>null</code>, this is not recommended, since a + * logging message without text is unlikely to be helpful. + */ + public void config(String message) + { + synchronized (lock) + { + if (isLoggable(Level.CONFIG)) + log(Level.CONFIG, message); + } + } + + /** + * Logs a message with severity level FINE. {@link Level#FINE} is intended for + * messages that are relevant for developers using the component generating + * log messages. Examples include minor, recoverable failures, or possible + * inefficiencies. + * + * @param message the message text, also used as look-up key if the logger is + * localizing messages with a resource bundle. While it is possible + * to pass <code>null</code>, this is not recommended, since a + * logging message without text is unlikely to be helpful. + */ + public void fine(String message) + { + synchronized (lock) + { + if (isLoggable(Level.FINE)) + log(Level.FINE, message); + } + } + + /** + * Logs a message with severity level FINER. {@link Level#FINER} is intended + * for rather detailed tracing, for example entering a method, returning from + * a method, or throwing an exception. + * + * @param message the message text, also used as look-up key if the logger is + * localizing messages with a resource bundle. While it is possible + * to pass <code>null</code>, this is not recommended, since a + * logging message without text is unlikely to be helpful. + */ + public void finer(String message) + { + synchronized (lock) + { + if (isLoggable(Level.FINER)) + log(Level.FINER, message); + } + } + + /** + * Logs a message with severity level FINEST. {@link Level#FINEST} is intended + * for highly detailed tracing, for example reaching a certain point inside + * the body of a method. + * + * @param message the message text, also used as look-up key if the logger is + * localizing messages with a resource bundle. While it is possible + * to pass <code>null</code>, this is not recommended, since a + * logging message without text is unlikely to be helpful. + */ + public void finest(String message) + { + synchronized (lock) + { + if (isLoggable(Level.FINEST)) + log(Level.FINEST, message); + } + } + + /** + * Adds a handler to the set of handlers that get notified when a log record + * is to be published. + * + * @param handler the handler to be added. + * @throws NullPointerException if <code>handler</code> is <code>null</code>. + * @throws SecurityException if this logger is not anonymous, a security + * manager exists, and the caller is not granted the permission to + * control the logging infrastructure by having + * LoggingPermission("control"). Untrusted code can obtain an + * anonymous logger through the static factory method + * {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}. + */ + public void addHandler(Handler handler) throws SecurityException + { + synchronized (lock) + { + if (handler == null) + throw new NullPointerException(); + + /* + * An application is allowed to control an anonymous logger without + * having the permission to control the logging infrastructure. + */ + if (! anonymous) + LogManager.getLogManager().checkAccess(); + + if (! handlerList.contains(handler)) + { + handlerList.add(handler); + handlers = getHandlers(); + } + } + } + + /** + * Removes a handler from the set of handlers that get notified when a log + * record is to be published. + * + * @param handler the handler to be removed. + * @throws SecurityException if this logger is not anonymous, a security + * manager exists, and the caller is not granted the permission to + * control the logging infrastructure by having + * LoggingPermission("control"). Untrusted code can obtain an + * anonymous logger through the static factory method {@link + * #getAnonymousLogger(java.lang.String) getAnonymousLogger}. + * @throws NullPointerException if <code>handler</code> is <code>null</code>. + */ + public void removeHandler(Handler handler) throws SecurityException + { + synchronized (lock) + { + /* + * An application is allowed to control an anonymous logger without + * having the permission to control the logging infrastructure. + */ + if (! anonymous) + LogManager.getLogManager().checkAccess(); + + if (handler == null) + throw new NullPointerException(); + + handlerList.remove(handler); + handlers = getHandlers(); + } + } + + /** + * Returns the handlers currently registered for this Logger. When a log + * record has been deemed as being loggable, it will be passed to all + * registered handlers for publication. In addition, if the logger uses parent + * handlers (see {@link #getUseParentHandlers() getUseParentHandlers} and + * {@link #setUseParentHandlers(boolean) setUseParentHandlers}, the log + * record will be passed to the parent's handlers. + */ + public Handler[] getHandlers() + { + synchronized (lock) + { + /* + * We cannot return our internal handlers array because we do not have + * any guarantee that the caller would not change the array entries. + */ + return (Handler[]) handlerList.toArray(new Handler[handlerList.size()]); + } + } + + /** + * Returns whether or not this Logger forwards log records to handlers + * registered for its parent loggers. + * + * @return <code>false</code> if this Logger sends log records merely to + * Handlers registered with itself; <code>true</code> if this Logger + * sends log records not only to Handlers registered with itself, but + * also to those Handlers registered with parent loggers. + */ + public boolean getUseParentHandlers() + { + synchronized (lock) + { + return useParentHandlers; + } + } + + /** + * Sets whether or not this Logger forwards log records to handlers registered + * for its parent loggers. + * + * @param useParentHandlers <code>false</code> to let this Logger send log + * records merely to Handlers registered with itself; + * <code>true</code> to let this Logger send log records not only + * to Handlers registered with itself, but also to those Handlers + * registered with parent loggers. + * @throws SecurityException if this logger is not anonymous, a security + * manager exists, and the caller is not granted the permission to + * control the logging infrastructure by having + * LoggingPermission("control"). Untrusted code can obtain an + * anonymous logger through the static factory method + * {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}. + */ + public void setUseParentHandlers(boolean useParentHandlers) + { + synchronized (lock) + { + /* + * An application is allowed to control an anonymous logger without + * having the permission to control the logging infrastructure. + */ + if (! anonymous) + LogManager.getLogManager().checkAccess(); + + this.useParentHandlers = useParentHandlers; + } + } + + /** + * Returns the parent of this logger. By default, the parent is assigned by + * the LogManager by inspecting the logger's name. + * + * @return the parent of this logger (as detemined by the LogManager by + * inspecting logger names), the root logger if no other logger has a + * name which is a prefix of this logger's name, or <code>null</code> + * for the root logger. + */ + public Logger getParent() + { + synchronized (lock) + { + return parent; + } + } + + /** + * Sets the parent of this logger. Usually, applications do not call this + * method directly. Instead, the LogManager will ensure that the tree of + * loggers reflects the hierarchical logger namespace. Basically, this method + * should not be public at all, but the GNU implementation follows the API + * specification. + * + * @throws NullPointerException if <code>parent</code> is <code>null</code>. + * @throws SecurityException if this logger is not anonymous, a security + * manager exists, and the caller is not granted the permission to + * control the logging infrastructure by having + * LoggingPermission("control"). Untrusted code can obtain an + * anonymous logger through the static factory method + * {@link #getAnonymousLogger(java.lang.String) getAnonymousLogger}. + */ + public void setParent(Logger parent) + { + synchronized (lock) + { + if (parent == null) + throw new NullPointerException(); + + if (this == root) + throw new IllegalArgumentException( + "the root logger can only have a null parent"); + + /* + * An application is allowed to control an anonymous logger without + * having the permission to control the logging infrastructure. + */ + if (! anonymous) + LogManager.getLogManager().checkAccess(); + + this.parent = parent; + } + } + + /** + * Gets the StackTraceElement of the first class that is not this class. That + * should be the initial caller of a logging method. + * + * @return caller of the initial logging method or null if unknown. + */ + private StackTraceElement getCallerStackFrame() + { + Throwable t = new Throwable(); + StackTraceElement[] stackTrace = t.getStackTrace(); + int index = 0; + + // skip to stackentries until this class + while (index < stackTrace.length + && ! stackTrace[index].getClassName().equals(getClass().getName())) + index++; + + // skip the stackentries of this class + while (index < stackTrace.length + && stackTrace[index].getClassName().equals(getClass().getName())) + index++; + + return index < stackTrace.length ? stackTrace[index] : null; + } + + /** + * Reset and close handlers attached to this logger. This function is package + * private because it must only be available to the LogManager. + */ + void resetLogger() + { + for (int i = 0; i < handlers.length; i++) + { + handlers[i].close(); + handlerList.remove(handlers[i]); + } + handlers = getHandlers(); + } +} diff --git a/libjava/classpath/java/util/logging/LoggingMXBean.java b/libjava/classpath/java/util/logging/LoggingMXBean.java new file mode 100644 index 000000000..24d8834c7 --- /dev/null +++ b/libjava/classpath/java/util/logging/LoggingMXBean.java @@ -0,0 +1,85 @@ +/* LoggingMxBean.java -- Management interface for logging + Copyright (C) 2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import java.util.List; + +/** + * This interface represents the management interface for logging. + * There is a single logging bean per VM instance, which can be + * retrieved via {@link LogManager#getLoggingMXBean()}. + * + * @since 1.5 + */ +public interface LoggingMXBean +{ + /** + * Return the name of the logging level given the name of + * a logger. Returns null if no such logger exists. + * @param logger the logger's name + * @return the logging level's name, or null + */ + String getLoggerLevel(String logger); + + /** + * Return a list of all logger names. + */ + List<String> getLoggerNames(); + + /** + * Return the name of the parent of the indicated logger. + * If no such logger exists, returns null. If the logger + * is the root logger, returns the empty string. + * @param logger the logger's name + * @return the name of the logger's parent, or null + */ + String getParentLoggerName(String logger); + + /** + * Sets the logging level for a particular logger. + * + * @param logger the name of the logger + * @param level the name of the new logging level, or null + * @throws IllegalArgumentException if the level is not + * recognized, or if the logger does not exist + * @throws SecurityException if access is denied; + * see {@link Logger#setLevel(Level)} + */ + void setLoggerLevel(String logger, String level); +} diff --git a/libjava/classpath/java/util/logging/LoggingPermission.java b/libjava/classpath/java/util/logging/LoggingPermission.java new file mode 100644 index 000000000..804fb9401 --- /dev/null +++ b/libjava/classpath/java/util/logging/LoggingPermission.java @@ -0,0 +1,75 @@ +/* LoggingPermission.java -- a class for logging permissions. + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +public final class LoggingPermission + extends java.security.BasicPermission +{ + private static final long serialVersionUID = 63564341580231582L; + + /** + * Creates a new LoggingPermission. + * + * @param name the name of the permission, which must be "control". + * + * @param actions the list of actions for the permission, which + * must be either <code>null</code> or an empty + * string. + * + * @exception IllegalArgumentException if <code>name</code> + * is not "control", or <code>actions</code> is + * neither <code>null</code> nor empty. + */ + public LoggingPermission(String name, String actions) + { + super("control", ""); + + if (!"control".equals(name)) + { + throw new IllegalArgumentException( + "name of LoggingPermission must be \"control\""); + } + + if ((actions != null) && (actions.length() != 0)) + { + throw new IllegalArgumentException( + "actions of LoggingPermissions must be null or empty"); + } + } +} diff --git a/libjava/classpath/java/util/logging/MemoryHandler.java b/libjava/classpath/java/util/logging/MemoryHandler.java new file mode 100644 index 000000000..e5c258bbf --- /dev/null +++ b/libjava/classpath/java/util/logging/MemoryHandler.java @@ -0,0 +1,345 @@ +/* MemoryHandler.java -- a class for buffering log messages in a memory buffer + Copyright (C) 2002, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.util.logging; + +/** + * A <code>MemoryHandler</code> maintains a circular buffer of + * log records. + * + * <p><strong>Configuration:</strong> Values of the subsequent + * <code>LogManager</code> properties are taken into consideration + * when a <code>MemoryHandler</code> is initialized. + * If a property is not defined, or if it has an invalid + * value, a default is taken without an exception being thrown. + * + * <ul> + * <li><code>java.util.MemoryHandler.level</code> - specifies + * the initial severity level threshold. Default value: + * <code>Level.ALL</code>.</li> + * <li><code>java.util.MemoryHandler.filter</code> - specifies + * the name of a Filter class. Default value: No Filter.</li> + * <li><code>java.util.MemoryHandler.size</code> - specifies the + * maximum number of log records that are kept in the circular + * buffer. Default value: 1000.</li> + * <li><code>java.util.MemoryHandler.push</code> - specifies the + * <code>pushLevel</code>. Default value: + * <code>Level.SEVERE</code>.</li> + * <li><code>java.util.MemoryHandler.target</code> - specifies the + * name of a subclass of {@link Handler} that will be used as the + * target handler. There is no default value for this property; + * if it is not set, the no-argument MemoryHandler constructor + * will throw an exception.</li> + * </ul> + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class MemoryHandler + extends Handler +{ + /** + * The storage area used for buffering the unpushed log records in + * memory. + */ + private final LogRecord[] buffer; + + + /** + * The current position in the circular buffer. For a new + * MemoryHandler, or immediately after {@link #push()} was called, + * the value of this variable is zero. Each call to {@link + * #publish(LogRecord)} will store the published LogRecord into + * <code>buffer[position]</code> before position is incremented by + * one. If position becomes greater than the size of the buffer, it + * is reset to zero. + */ + private int position; + + + /** + * The number of log records which have been published, but not + * pushed yet to the target handler. + */ + private int numPublished; + + + /** + * The push level threshold for this <code>Handler</code>. When a + * record is published whose severity level is greater than or equal + * to the <code>pushLevel</code> of this <code>MemoryHandler</code>, + * the {@link #push()} method will be invoked for pushing the buffer + * contents to the target <code>Handler</code>. + */ + private Level pushLevel; + + + /** + * The Handler to which log records are forwarded for actual + * publication. + */ + private final Handler target; + + + /** + * Constructs a <code>MemoryHandler</code> for keeping a circular + * buffer of LogRecords; the initial configuration is determined by + * the <code>LogManager</code> properties described above. + */ + public MemoryHandler() + { + this((Handler) LogManager.getInstanceProperty( + "java.util.logging.MemoryHandler.target", + Handler.class, /* default */ null), + LogManager.getIntPropertyClamped( + "java.util.logging.MemoryHandler.size", + /* default */ 1000, + /* minimum value */ 1, + /* maximum value */ Integer.MAX_VALUE), + LogManager.getLevelProperty( + "java.util.logging.MemoryHandler.push", + /* default push level */ Level.SEVERE)); + } + + + /** + * Constructs a <code>MemoryHandler</code> for keeping a circular + * buffer of LogRecords, given some parameters. The values of the + * other parameters are taken from LogManager properties, as + * described above. + * + * @param target the target handler that will receive those + * log records that are passed on for publication. + * + * @param size the number of log records that are kept in the buffer. + * The value must be a at least one. + * + * @param pushLevel the push level threshold for this + * <code>MemoryHandler</code>. When a record is published whose + * severity level is greater than or equal to + * <code>pushLevel</code>, the {@link #push()} method will be + * invoked in order to push the bufffer contents to + * <code>target</code>. + * + * @throws java.lang.IllegalArgumentException if <code>size</code> + * is negative or zero. The GNU implementation also throws + * an IllegalArgumentException if <code>target</code> or + * <code>pushLevel</code> are <code>null</code>, but the + * API specification does not prescribe what should happen + * in those cases. + */ + public MemoryHandler(Handler target, int size, Level pushLevel) + { + if ((target == null) || (size <= 0) || (pushLevel == null)) + throw new IllegalArgumentException(); + + buffer = new LogRecord[size]; + this.pushLevel = pushLevel; + this.target = target; + + setLevel(LogManager.getLevelProperty( + "java.util.logging.MemoryHandler.level", + /* default value */ Level.ALL)); + + setFilter((Filter) LogManager.getInstanceProperty( + "java.util.logging.MemoryHandler.filter", + /* must be instance of */ Filter.class, + /* default value */ null)); + } + + + /** + * Stores a <code>LogRecord</code> in a fixed-size circular buffer, + * provided the record passes all tests for being loggable. If the + * buffer is full, the oldest record will be discarded. + * + * <p>If the record has a severity level which is greater than or + * equal to the <code>pushLevel</code> of this + * <code>MemoryHandler</code>, the {@link #push()} method will be + * invoked for pushing the buffer contents to the target + * <code>Handler</code>. + * + * <p>Most applications do not need to call this method directly. + * Instead, they will use use a {@link Logger}, which will create + * LogRecords and distribute them to registered handlers. + * + * @param record the log event to be published. + */ + public void publish(LogRecord record) + { + if (!isLoggable(record)) + return; + + buffer[position] = record; + position = (position + 1) % buffer.length; + numPublished = numPublished + 1; + + if (record.getLevel().intValue() >= pushLevel.intValue()) + push(); + } + + + /** + * Pushes the contents of the memory buffer to the target + * <code>Handler</code> and clears the buffer. Note that + * the target handler will discard those records that do + * not satisfy its own severity level threshold, or that are + * not considered loggable by an installed {@link Filter}. + * + * <p>In case of an I/O failure, the {@link ErrorManager} of the + * target <code>Handler</code> will be notified, but the caller of + * this method will not receive an exception. + */ + public void push() + { + int i; + + if (numPublished < buffer.length) + { + for (i = 0; i < position; i++) + target.publish(buffer[i]); + } + else + { + for (i = position; i < buffer.length; i++) + target.publish(buffer[i]); + for (i = 0; i < position; i++) + target.publish(buffer[i]); + } + + numPublished = 0; + position = 0; + } + + + /** + * Forces any data that may have been buffered by the target + * <code>Handler</code> to the underlying output device, but + * does <em>not</em> push the contents of the circular memory + * buffer to the target handler. + * + * <p>In case of an I/O failure, the {@link ErrorManager} of the + * target <code>Handler</code> will be notified, but the caller of + * this method will not receive an exception. + * + * @see #push() + */ + public void flush() + { + target.flush(); + } + + + /** + * Closes this <code>MemoryHandler</code> and its associated target + * handler, discarding the contents of the memory buffer. However, + * any data that may have been buffered by the target + * <code>Handler</code> is forced to the underlying output device. + * + * <p>As soon as <code>close</code> has been called, + * a <code>Handler</code> should not be used anymore. Attempts + * to publish log records, to flush buffers, or to modify the + * <code>Handler</code> in any other way may throw runtime + * exceptions after calling <code>close</code>.</p> + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> of + * the associated target <code>Handler</code> will be informed, but + * the caller of this method will not receive an exception.</p> + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + * + * @see #push() + */ + public void close() + throws SecurityException + { + push(); + + /* This will check for LoggingPermission("control"). If the + * current security context does not grant this permission, + * push() has been executed, but this does not impose a + * security risk. + */ + target.close(); + } + + + + /** + * Returns the push level threshold for this <code>Handler</code>. + * When a record is published whose severity level is greater + * than or equal to the <code>pushLevel</code> of this + * <code>MemoryHandler</code>, the {@link #push()} method will be + * invoked for pushing the buffer contents to the target + * <code>Handler</code>. + * + * @return the push level threshold for automatic pushing. + */ + public Level getPushLevel() + { + return pushLevel; + } + + + /** + * Sets the push level threshold for this <code>Handler</code>. + * When a record is published whose severity level is greater + * than or equal to the <code>pushLevel</code> of this + * <code>MemoryHandler</code>, the {@link #push()} method will be + * invoked for pushing the buffer contents to the target + * <code>Handler</code>. + * + * @param pushLevel the push level threshold for automatic pushing. + * + * @exception SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + * + * @exception NullPointerException if <code>pushLevel</code> is + * <code>null</code>. + */ + public void setPushLevel(Level pushLevel) + { + LogManager.getLogManager().checkAccess(); + + /* Throws a NullPointerException if pushLevel is null. */ + pushLevel.getClass(); + + this.pushLevel = pushLevel; + } +} diff --git a/libjava/classpath/java/util/logging/SimpleFormatter.java b/libjava/classpath/java/util/logging/SimpleFormatter.java new file mode 100644 index 000000000..da731f2b1 --- /dev/null +++ b/libjava/classpath/java/util/logging/SimpleFormatter.java @@ -0,0 +1,131 @@ +/* SimpleFormatter.java -- + A class for formatting log records into short human-readable messages + Copyright (C) 2002, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import gnu.java.lang.CPStringBuilder; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.DateFormat; +import java.util.Date; + +/** + * A <code>SimpleFormatter</code> formats log records into + * short human-readable messages, typically one or two lines. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class SimpleFormatter + extends Formatter +{ + /** + * Constructs a SimpleFormatter. + */ + public SimpleFormatter() + { + } + + + /** + * An instance of a DateFormatter that is used for formatting + * the time of a log record into a human-readable string, + * according to the rules of the current locale. The value + * is set after the first invocation of format, since it is + * common that a JVM will instantiate a SimpleFormatter without + * ever using it. + */ + private DateFormat dateFormat; + + /** + * The character sequence that is used to separate lines in the + * generated stream. Somewhat surprisingly, the Sun J2SE 1.4 + * reference implementation always uses UNIX line endings, even on + * platforms that have different line ending conventions (i.e., + * DOS). The GNU implementation does not replicate this bug. + * + * @see Sun bug parade, bug #4462871, + * "java.util.logging.SimpleFormatter uses hard-coded line separator". + */ + static final String lineSep = System.getProperty("line.separator"); + + + /** + * Formats a log record into a String. + * + * @param record the log record to be formatted. + * + * @return a short human-readable message, typically one or two + * lines. Lines are separated using the default platform line + * separator. + * + * @throws NullPointerException if <code>record</code> + * is <code>null</code>. + */ + public String format(LogRecord record) + { + CPStringBuilder buf = new CPStringBuilder(180); + + if (dateFormat == null) + dateFormat = DateFormat.getDateTimeInstance(); + + buf.append(dateFormat.format(new Date(record.getMillis()))); + buf.append(' '); + buf.append(record.getSourceClassName()); + buf.append(' '); + buf.append(record.getSourceMethodName()); + buf.append(lineSep); + + buf.append(record.getLevel()); + buf.append(": "); + buf.append(formatMessage(record)); + + buf.append(lineSep); + + Throwable throwable = record.getThrown(); + if (throwable != null) + { + StringWriter sink = new StringWriter(); + throwable.printStackTrace(new PrintWriter(sink, true)); + buf.append(sink.toString()); + } + + return buf.toString(); + } +} diff --git a/libjava/classpath/java/util/logging/SocketHandler.java b/libjava/classpath/java/util/logging/SocketHandler.java new file mode 100644 index 000000000..3c17b9bbc --- /dev/null +++ b/libjava/classpath/java/util/logging/SocketHandler.java @@ -0,0 +1,220 @@ +/* SocketHandler.java -- a class for publishing log messages to network sockets + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + + +/** + * A <code>SocketHandler</code> publishes log records to + * a TCP/IP socket. + * + * <p><strong>Configuration:</strong> Values of the subsequent + * <code>LogManager</code> properties are taken into consideration + * when a <code>SocketHandler</code> is initialized. + * If a property is not defined, or if it has an invalid + * value, a default is taken without an exception being thrown. + * + * <ul> + * + * <li><code>java.util.SocketHandler.level</code> - specifies + * the initial severity level threshold. Default value: + * <code>Level.ALL</code>.</li> + * + * <li><code>java.util.SocketHandler.filter</code> - specifies + * the name of a Filter class. Default value: No Filter.</li> + * + * <li><code>java.util.SocketHandler.formatter</code> - specifies + * the name of a Formatter class. Default value: + * <code>java.util.logging.XMLFormatter</code>.</li> + * + * <li><code>java.util.SocketHandler.encoding</code> - specifies + * the name of the character encoding. Default value: + * the default platform encoding.</li> + * + * <li><code>java.util.SocketHandler.host</code> - specifies + * the name of the host to which records are published. + * There is no default value for this property; if it is + * not set, the SocketHandler constructor will throw + * an exception.</li> + * + * <li><code>java.util.SocketHandler.port</code> - specifies + * the TCP/IP port to which records are published. + * There is no default value for this property; if it is + * not set, the SocketHandler constructor will throw + * an exception.</li> + * + * </ul> + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class SocketHandler + extends StreamHandler +{ + /** + * Constructs a <code>SocketHandler</code> that publishes log + * records to a TCP/IP socket. Tthe initial configuration is + * determined by the <code>LogManager</code> properties described + * above. + * + * @throws java.io.IOException if the connection to the specified + * network host and port cannot be established. + * + * @throws java.lang.IllegalArgumentException if either the + * <code>java.util.logging.SocketHandler.host</code> + * or <code>java.util.logging.SocketHandler.port</code> + * LogManager properties is not defined, or specifies + * an invalid value. + */ + public SocketHandler() + throws java.io.IOException + { + this(LogManager.getLogManager().getProperty("java.util.logging.SocketHandler.host"), + getPortNumber()); + } + + + /** + * Constructs a <code>SocketHandler</code> that publishes log + * records to a TCP/IP socket. With the exception of the internet + * host and port, the initial configuration is determined by the + * <code>LogManager</code> properties described above. + * + * @param host the Internet host to which log records will be + * forwarded. + * + * @param port the port at the host which will accept a request + * for a TCP/IP connection. + * + * @throws java.io.IOException if the connection to the specified + * network host and port cannot be established. + * + * @throws java.lang.IllegalArgumentException if either + * <code>host</code> or <code>port</code> specify + * an invalid value. + */ + public SocketHandler(String host, int port) + throws java.io.IOException + { + super(createSocket(host, port), + "java.util.logging.SocketHandler", + /* default level */ Level.ALL, + /* formatter */ null, + /* default formatter */ XMLFormatter.class); + } + + + /** + * Retrieves the port number from the java.util.logging.SocketHandler.port + * LogManager property. + * + * @throws IllegalArgumentException if the property is not defined or + * does not specify an integer value. + */ + private static int getPortNumber() + { + try { + return Integer.parseInt(LogManager.getLogManager().getProperty("java.util.logging.SocketHandler.port")); + } catch (Exception ex) { + throw new IllegalArgumentException(); + } + } + + + /** + * Creates an OutputStream for publishing log records to an Internet + * host and port. This private method is a helper for use by the + * constructor of SocketHandler. + * + * @param host the Internet host to which log records will be + * forwarded. + * + * @param port the port at the host which will accept a request + * for a TCP/IP connection. + * + * @throws java.io.IOException if the connection to the specified + * network host and port cannot be established. + * + * @throws java.lang.IllegalArgumentException if either + * <code>host</code> or <code>port</code> specify + * an invalid value. + */ + private static java.io.OutputStream createSocket(String host, int port) + throws java.io.IOException, java.lang.IllegalArgumentException + { + java.net.Socket socket; + + if ((host == null) || (port < 1)) + throw new IllegalArgumentException(); + + socket = new java.net.Socket(host, port); + + socket.shutdownInput(); + + /* The architecture of the logging framework provides replaceable + * formatters. Because these formatters perform their task by + * returning one single String for each LogRecord to be formatted, + * there is no need to buffer. + */ + socket.setTcpNoDelay(true); + + return socket.getOutputStream(); + } + + + /** + * Publishes a <code>LogRecord</code> to the network socket, + * provided the record passes all tests for being loggable. + * In addition, all data that may have been buffered will + * be forced to the network stream. + * + * <p>Most applications do not need to call this method directly. + * Instead, they will use a {@link Logger} instance, which will + * create LogRecords and distribute them to registered handlers. + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> + * of this <code>SocketHandler</code> will be informed, but the caller + * of this method will not receive an exception. + * + * @param record the log event to be published. + */ + public void publish(LogRecord record) + { + super.publish(record); + flush(); + } +} diff --git a/libjava/classpath/java/util/logging/StreamHandler.java b/libjava/classpath/java/util/logging/StreamHandler.java new file mode 100644 index 000000000..d74dfac0c --- /dev/null +++ b/libjava/classpath/java/util/logging/StreamHandler.java @@ -0,0 +1,521 @@ +/* StreamHandler.java -- + A class for publishing log messages to instances of java.io.OutputStream + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +/** + * A <code>StreamHandler</code> publishes <code>LogRecords</code> to + * a instances of <code>java.io.OutputStream</code>. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class StreamHandler + extends Handler +{ + private OutputStream out; + private Writer writer; + + + /** + * Indicates the current state of this StreamHandler. The value + * should be one of STATE_FRESH, STATE_PUBLISHED, or STATE_CLOSED. + */ + private int streamState = STATE_FRESH; + + + /** + * streamState having this value indicates that the StreamHandler + * has been created, but the publish(LogRecord) method has not been + * called yet. If the StreamHandler has been constructed without an + * OutputStream, writer will be null, otherwise it is set to a + * freshly created OutputStreamWriter. + */ + private static final int STATE_FRESH = 0; + + + /** + * streamState having this value indicates that the publish(LocRecord) + * method has been called at least once. + */ + private static final int STATE_PUBLISHED = 1; + + + /** + * streamState having this value indicates that the close() method + * has been called. + */ + private static final int STATE_CLOSED = 2; + + + /** + * Creates a <code>StreamHandler</code> without an output stream. + * Subclasses can later use {@link + * #setOutputStream(java.io.OutputStream)} to associate an output + * stream with this StreamHandler. + */ + public StreamHandler() + { + this(null, null); + } + + + /** + * Creates a <code>StreamHandler</code> that formats log messages + * with the specified Formatter and publishes them to the specified + * output stream. + * + * @param out the output stream to which the formatted log messages + * are published. + * + * @param formatter the <code>Formatter</code> that will be used + * to format log messages. + */ + public StreamHandler(OutputStream out, Formatter formatter) + { + this(out, "java.util.logging.StreamHandler", Level.INFO, + formatter, SimpleFormatter.class); + } + + + StreamHandler( + OutputStream out, + String propertyPrefix, + Level defaultLevel, + Formatter formatter, Class defaultFormatterClass) + { + this.level = LogManager.getLevelProperty(propertyPrefix + ".level", + defaultLevel); + + this.filter = (Filter) LogManager.getInstanceProperty( + propertyPrefix + ".filter", + /* must be instance of */ Filter.class, + /* default: new instance of */ null); + + if (formatter != null) + this.formatter = formatter; + else + this.formatter = (Formatter) LogManager.getInstanceProperty( + propertyPrefix + ".formatter", + /* must be instance of */ Formatter.class, + /* default: new instance of */ defaultFormatterClass); + + try + { + String enc = LogManager.getLogManager().getProperty(propertyPrefix + + ".encoding"); + + /* make sure enc actually is a valid encoding */ + if ((enc != null) && (enc.length() > 0)) + new String(new byte[0], enc); + + this.encoding = enc; + } + catch (Exception _) + { + } + + if (out != null) + { + try + { + changeWriter(out, getEncoding()); + } + catch (UnsupportedEncodingException uex) + { + /* This should never happen, since the validity of the encoding + * name has been checked above. + */ + throw new RuntimeException(uex.getMessage()); + } + } + } + + + private void checkOpen() + { + if (streamState == STATE_CLOSED) + throw new IllegalStateException(this.toString() + " has been closed"); + } + + private void checkFresh() + { + checkOpen(); + if (streamState != STATE_FRESH) + throw new IllegalStateException("some log records have been published to " + this); + } + + + private void changeWriter(OutputStream out, String encoding) + throws UnsupportedEncodingException + { + OutputStreamWriter writer; + + /* The logging API says that a null encoding means the default + * platform encoding. However, java.io.OutputStreamWriter needs + * another constructor for the default platform encoding, + * passing null would throw an exception. + */ + if (encoding == null) + writer = new OutputStreamWriter(out); + else + writer = new OutputStreamWriter(out, encoding); + + /* Closing the stream has side effects -- do this only after + * creating a new writer has been successful. + */ + if ((streamState != STATE_FRESH) || (this.writer != null)) + close(); + + this.writer = writer; + this.out = out; + this.encoding = encoding; + streamState = STATE_FRESH; + } + + + /** + * Sets the character encoding which this handler uses for publishing + * log records. The encoding of a <code>StreamHandler</code> must be + * set before any log records have been published. + * + * @param encoding the name of a character encoding, or <code>null</code> + * for the default encoding. + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control the + * the logging infrastructure. + * + * @exception IllegalStateException if any log records have been + * published to this <code>StreamHandler</code> before. Please + * be aware that this is a pecularity of the GNU implementation. + * While the API specification indicates that it is an error + * if the encoding is set after records have been published, + * it does not mandate any specific behavior for that case. + */ + public void setEncoding(String encoding) + throws SecurityException, UnsupportedEncodingException + { + /* The inherited implementation first checks whether the invoking + * code indeed has the permission to control the logging infra- + * structure, and throws a SecurityException if this was not the + * case. + * + * Next, it verifies that the encoding is supported and throws + * an UnsupportedEncodingExcpetion otherwise. Finally, it remembers + * the name of the encoding. + */ + super.setEncoding(encoding); + + checkFresh(); + + /* If out is null, setEncoding is being called before an output + * stream has been set. In that case, we need to check that the + * encoding is valid, and remember it if this is the case. Since + * this is exactly what the inherited implementation of + * Handler.setEncoding does, we can delegate. + */ + if (out != null) + { + /* The logging API says that a null encoding means the default + * platform encoding. However, java.io.OutputStreamWriter needs + * another constructor for the default platform encoding, passing + * null would throw an exception. + */ + if (encoding == null) + writer = new OutputStreamWriter(out); + else + writer = new OutputStreamWriter(out, encoding); + } + } + + + /** + * Changes the output stream to which this handler publishes + * logging records. + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + * + * @throws NullPointerException if <code>out</code> + * is <code>null</code>. + */ + protected void setOutputStream(OutputStream out) + throws SecurityException + { + LogManager.getLogManager().checkAccess(); + + /* Throw a NullPointerException if out is null. */ + out.getClass(); + + try + { + changeWriter(out, getEncoding()); + } + catch (UnsupportedEncodingException ex) + { + /* This seems quite unlikely to happen, unless the underlying + * implementation of java.io.OutputStreamWriter changes its + * mind (at runtime) about the set of supported character + * encodings. + */ + throw new RuntimeException(ex.getMessage()); + } + } + + + /** + * Publishes a <code>LogRecord</code> to the associated output + * stream, provided the record passes all tests for being loggable. + * The <code>StreamHandler</code> will localize the message of the + * log record and substitute any message parameters. + * + * <p>Most applications do not need to call this method directly. + * Instead, they will use use a {@link Logger}, which will create + * LogRecords and distribute them to registered handlers. + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> + * of this <code>Handler</code> will be informed, but the caller + * of this method will not receive an exception. + * + * <p>If a log record is being published to a + * <code>StreamHandler</code> that has been closed earlier, the Sun + * J2SE 1.4 reference can be observed to silently ignore the + * call. The GNU implementation, however, intentionally behaves + * differently by informing the <code>ErrorManager</code> associated + * with this <code>StreamHandler</code>. Since the condition + * indicates a programming error, the programmer should be + * informed. It also seems extremely unlikely that any application + * would depend on the exact behavior in this rather obscure, + * erroneous case -- especially since the API specification does not + * prescribe what is supposed to happen. + * + * @param record the log event to be published. + */ + public void publish(LogRecord record) + { + String formattedMessage; + + if (!isLoggable(record)) + return; + + if (streamState == STATE_FRESH) + { + try + { + writer.write(formatter.getHead(this)); + } + catch (java.io.IOException ex) + { + reportError(null, ex, ErrorManager.WRITE_FAILURE); + return; + } + catch (Exception ex) + { + reportError(null, ex, ErrorManager.GENERIC_FAILURE); + return; + } + + streamState = STATE_PUBLISHED; + } + + try + { + formattedMessage = formatter.format(record); + } + catch (Exception ex) + { + reportError(null, ex, ErrorManager.FORMAT_FAILURE); + return; + } + + try + { + writer.write(formattedMessage); + } + catch (Exception ex) + { + reportError(null, ex, ErrorManager.WRITE_FAILURE); + } + } + + + /** + * Checks whether or not a <code>LogRecord</code> would be logged + * if it was passed to this <code>StreamHandler</code> for publication. + * + * <p>The <code>StreamHandler</code> implementation first checks + * whether a writer is present and the handler's level is greater + * than or equal to the severity level threshold. In a second step, + * if a {@link Filter} has been installed, its {@link + * Filter#isLoggable(LogRecord) isLoggable} method is + * invoked. Subclasses of <code>StreamHandler</code> can override + * this method to impose their own constraints. + * + * @param record the <code>LogRecord</code> to be checked. + * + * @return <code>true</code> if <code>record</code> would + * be published by {@link #publish(LogRecord) publish}, + * <code>false</code> if it would be discarded. + * + * @see #setLevel(Level) + * @see #setFilter(Filter) + * @see Filter#isLoggable(LogRecord) + * + * @throws NullPointerException if <code>record</code> is + * <code>null</code>. */ + public boolean isLoggable(LogRecord record) + { + return (writer != null) && super.isLoggable(record); + } + + + /** + * Forces any data that may have been buffered to the underlying + * output device. + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> + * of this <code>Handler</code> will be informed, but the caller + * of this method will not receive an exception. + * + * <p>If a <code>StreamHandler</code> that has been closed earlier + * is closed a second time, the Sun J2SE 1.4 reference can be + * observed to silently ignore the call. The GNU implementation, + * however, intentionally behaves differently by informing the + * <code>ErrorManager</code> associated with this + * <code>StreamHandler</code>. Since the condition indicates a + * programming error, the programmer should be informed. It also + * seems extremely unlikely that any application would depend on the + * exact behavior in this rather obscure, erroneous case -- + * especially since the API specification does not prescribe what is + * supposed to happen. + */ + public void flush() + { + try + { + checkOpen(); + if (writer != null) + writer.flush(); + } + catch (Exception ex) + { + reportError(null, ex, ErrorManager.FLUSH_FAILURE); + } + } + + + /** + * Closes this <code>StreamHandler</code> after having forced any + * data that may have been buffered to the underlying output + * device. + * + * <p>As soon as <code>close</code> has been called, + * a <code>Handler</code> should not be used anymore. Attempts + * to publish log records, to flush buffers, or to modify the + * <code>Handler</code> in any other way may throw runtime + * exceptions after calling <code>close</code>.</p> + * + * <p>In case of an I/O failure, the <code>ErrorManager</code> + * of this <code>Handler</code> will be informed, but the caller + * of this method will not receive an exception.</p> + * + * <p>If a <code>StreamHandler</code> that has been closed earlier + * is closed a second time, the Sun J2SE 1.4 reference can be + * observed to silently ignore the call. The GNU implementation, + * however, intentionally behaves differently by informing the + * <code>ErrorManager</code> associated with this + * <code>StreamHandler</code>. Since the condition indicates a + * programming error, the programmer should be informed. It also + * seems extremely unlikely that any application would depend on the + * exact behavior in this rather obscure, erroneous case -- + * especially since the API specification does not prescribe what is + * supposed to happen. + * + * @throws SecurityException if a security manager exists and + * the caller is not granted the permission to control + * the logging infrastructure. + */ + public void close() + throws SecurityException + { + LogManager.getLogManager().checkAccess(); + + try + { + /* Although flush also calls checkOpen, it catches + * any exceptions and reports them to the ErrorManager + * as flush failures. However, we want to report + * a closed stream as a close failure, not as a + * flush failure here. Therefore, we call checkOpen() + * before flush(). + */ + checkOpen(); + flush(); + + if (writer != null) + { + if (formatter != null) + { + /* Even if the StreamHandler has never published a record, + * it emits head and tail upon closing. An earlier version + * of the GNU Classpath implementation did not emitted + * anything. However, this had caused XML log files to be + * entirely empty instead of containing no log records. + */ + if (streamState == STATE_FRESH) + writer.write(formatter.getHead(this)); + if (streamState != STATE_CLOSED) + writer.write(formatter.getTail(this)); + } + streamState = STATE_CLOSED; + writer.close(); + } + } + catch (Exception ex) + { + reportError(null, ex, ErrorManager.CLOSE_FAILURE); + } + } +} diff --git a/libjava/classpath/java/util/logging/XMLFormatter.java b/libjava/classpath/java/util/logging/XMLFormatter.java new file mode 100644 index 000000000..da7c6ef29 --- /dev/null +++ b/libjava/classpath/java/util/logging/XMLFormatter.java @@ -0,0 +1,389 @@ +/* XMLFormatter.java -- + A class for formatting log messages into a standard XML format + Copyright (C) 2002, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.util.logging; + +import gnu.java.lang.CPStringBuilder; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.ResourceBundle; + +/** + * An <code>XMLFormatter</code> formats LogRecords into + * a standard XML format. + * + * @author Sascha Brawer (brawer@acm.org) + */ +public class XMLFormatter + extends Formatter +{ + /** + * Constructs a new XMLFormatter. + */ + public XMLFormatter() + { + } + + + /** + * The character sequence that is used to separate lines in the + * generated XML stream. Somewhat surprisingly, the Sun J2SE 1.4 + * reference implementation always uses UNIX line endings, even on + * platforms that have different line ending conventions (i.e., + * DOS). The GNU Classpath implementation does not replicates this + * bug. + * + * See also the Sun bug parade, bug #4462871, + * "java.util.logging.SimpleFormatter uses hard-coded line separator". + */ + private static final String lineSep = SimpleFormatter.lineSep; + + + /** + * A DateFormat for emitting time in the ISO 8601 format. + * Since the API specification of SimpleDateFormat does not talk + * about its thread-safety, we cannot share a singleton instance. + */ + private final SimpleDateFormat iso8601 + = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + + /** + * Appends a line consisting of indentation, opening element tag, + * element content, closing element tag and line separator to + * a CPStringBuilder, provided that the element content is + * actually existing. + * + * @param buf the CPStringBuilder to which the line will be appended. + * + * @param indent the indentation level. + * + * @param tag the element tag name, for instance <code>method</code>. + * + * @param content the element content, or <code>null</code> to + * have no output whatsoever appended to <code>buf</code>. + */ + private static void appendTag(CPStringBuilder buf, int indent, + String tag, String content) + { + int i; + + if (content == null) + return; + + for (i = 0; i < indent * 2; i++) + buf.append(' '); + + buf.append("<"); + buf.append(tag); + buf.append('>'); + + /* Append the content, but escape for XML by replacing + * '&', '<', '>' and all non-ASCII characters with + * appropriate escape sequences. + * The Sun J2SE 1.4 reference implementation does not + * escape non-ASCII characters. This is a bug in their + * implementation which has been reported in the Java + * bug parade as bug number (FIXME: Insert number here). + */ + for (i = 0; i < content.length(); i++) + { + char c = content.charAt(i); + switch (c) + { + case '&': + buf.append("&"); + break; + + case '<': + buf.append("<"); + break; + + case '>': + buf.append(">"); + break; + + default: + if (((c >= 0x20) && (c <= 0x7e)) + || (c == /* line feed */ 10) + || (c == /* carriage return */ 13)) + buf.append(c); + else + { + buf.append("&#"); + buf.append((int) c); + buf.append(';'); + } + break; + } /* switch (c) */ + } /* for i */ + + buf.append("</"); + buf.append(tag); + buf.append(">"); + buf.append(lineSep); + } + + + /** + * Appends a line consisting of indentation, opening element tag, + * numeric element content, closing element tag and line separator + * to a CPStringBuilder. + * + * @param buf the CPStringBuilder to which the line will be appended. + * + * @param indent the indentation level. + * + * @param tag the element tag name, for instance <code>method</code>. + * + * @param content the element content. + */ + private static void appendTag(CPStringBuilder buf, int indent, + String tag, long content) + { + appendTag(buf, indent, tag, Long.toString(content)); + } + + + public String format(LogRecord record) + { + CPStringBuilder buf = new CPStringBuilder(400); + Level level = record.getLevel(); + long millis = record.getMillis(); + Object[] params = record.getParameters(); + ResourceBundle bundle = record.getResourceBundle(); + String message; + + buf.append("<record>"); + buf.append(lineSep); + + + appendTag(buf, 1, "date", iso8601.format(new Date(millis))); + appendTag(buf, 1, "millis", millis); + appendTag(buf, 1, "sequence", record.getSequenceNumber()); + appendTag(buf, 1, "logger", record.getLoggerName()); + + if (level.isStandardLevel()) + appendTag(buf, 1, "level", level.toString()); + else + appendTag(buf, 1, "level", level.intValue()); + + appendTag(buf, 1, "class", record.getSourceClassName()); + appendTag(buf, 1, "method", record.getSourceMethodName()); + appendTag(buf, 1, "thread", record.getThreadID()); + + /* The Sun J2SE 1.4 reference implementation does not emit the + * message in localized form. This is in violation of the API + * specification. The GNU Classpath implementation intentionally + * replicates the buggy behavior of the Sun implementation, as + * different log files might be a big nuisance to users. + */ + try + { + record.setResourceBundle(null); + message = formatMessage(record); + } + finally + { + record.setResourceBundle(bundle); + } + appendTag(buf, 1, "message", message); + + /* The Sun J2SE 1.4 reference implementation does not + * emit key, catalog and param tags. This is in violation + * of the API specification. The Classpath implementation + * intentionally replicates the buggy behavior of the + * Sun implementation, as different log files might be + * a big nuisance to users. + * + * FIXME: File a bug report with Sun. Insert bug number here. + * + * + * key = record.getMessage(); + * if (key == null) + * key = ""; + * + * if ((bundle != null) && !key.equals(message)) + * { + * appendTag(buf, 1, "key", key); + * appendTag(buf, 1, "catalog", record.getResourceBundleName()); + * } + * + * if (params != null) + * { + * for (int i = 0; i < params.length; i++) + * appendTag(buf, 1, "param", params[i].toString()); + * } + */ + + /* FIXME: We have no way to obtain the stacktrace before free JVMs + * support the corresponding method in java.lang.Throwable. Well, + * it would be possible to parse the output of printStackTrace, + * but this would be pretty kludgy. Instead, we postpose the + * implementation until Throwable has made progress. + */ + Throwable thrown = record.getThrown(); + if (thrown != null) + { + buf.append(" <exception>"); + buf.append(lineSep); + + /* The API specification is not clear about what exactly + * goes into the XML record for a thrown exception: It + * could be the result of getMessage(), getLocalizedMessage(), + * or toString(). Therefore, it was necessary to write a + * Mauve testlet and run it with the Sun J2SE 1.4 reference + * implementation. It turned out that the we need to call + * toString(). + * + * FIXME: File a bug report with Sun, asking for clearer + * specs. + */ + appendTag(buf, 2, "message", thrown.toString()); + + /* FIXME: The Logging DTD specifies: + * + * <!ELEMENT exception (message?, frame+)> + * + * However, java.lang.Throwable.getStackTrace() is + * allowed to return an empty array. So, what frame should + * be emitted for an empty stack trace? We probably + * should file a bug report with Sun, asking for the DTD + * to be changed. + */ + + buf.append(" </exception>"); + buf.append(lineSep); + } + + + buf.append("</record>"); + buf.append(lineSep); + + return buf.toString(); + } + + + /** + * Returns a string that handlers are supposed to emit before + * the first log record. The base implementation returns an + * empty string, but subclasses such as {@link XMLFormatter} + * override this method in order to provide a suitable header. + * + * @return a string for the header. + * + * @param h the handler which will prepend the returned + * string in front of the first log record. This method + * will inspect certain properties of the handler, for + * example its encoding, in order to construct the header. + */ + public String getHead(Handler h) + { + CPStringBuilder buf; + String encoding; + + buf = new CPStringBuilder(80); + buf.append("<?xml version=\"1.0\" encoding=\""); + + encoding = h.getEncoding(); + + /* file.encoding is a system property with the Sun JVM, indicating + * the platform-default file encoding. Unfortunately, the API + * specification for java.lang.System.getProperties() does not + * list this property. + */ + if (encoding == null) + encoding = System.getProperty("file.encoding"); + + /* Since file.encoding is not listed with the API specification of + * java.lang.System.getProperties(), there might be some VMs that + * do not define this system property. Therefore, we use UTF-8 as + * a reasonable default. Please note that if the platform encoding + * uses the same codepoints as US-ASCII for the US-ASCII character + * set (e.g, 65 for A), it does not matter whether we emit the + * wrong encoding into the XML header -- the GNU Classpath will + * emit XML escape sequences like Ӓ for any non-ASCII + * character. Virtually all character encodings use the same code + * points as US-ASCII for ASCII characters. Probably, EBCDIC is + * the only exception. + */ + if (encoding == null) + encoding = "UTF-8"; + + /* On Windows XP localized for Swiss German (this is one of + * my [Sascha Brawer's] test machines), the default encoding + * has the canonical name "windows-1252". The "historical" name + * of this encoding is "Cp1252" (see the Javadoc for the class + * java.nio.charset.Charset for the distinction). Now, that class + * does have a method for mapping historical to canonical encoding + * names. However, if we used it here, we would be come dependent + * on java.nio.*, which was only introduced with J2SE 1.4. + * Thus, we do this little hack here. As soon as Classpath supports + * java.nio.charset.CharSet, this hack should be replaced by + * code that correctly canonicalizes the encoding name. + */ + if ((encoding.length() > 2) && encoding.startsWith("Cp")) + encoding = "windows-" + encoding.substring(2); + + buf.append(encoding); + + buf.append("\" standalone=\"no\"?>"); + buf.append(lineSep); + + /* SYSTEM is not a fully qualified URL so that validating + * XML parsers do not need to connect to the Internet in + * order to read in a log file. See also the Sun Bug Parade, + * bug #4372790, "Logging APIs: need to use relative URL for XML + * doctype". + */ + buf.append("<!DOCTYPE log SYSTEM \"logger.dtd\">"); + buf.append(lineSep); + buf.append("<log>"); + buf.append(lineSep); + + return buf.toString(); + } + + + public String getTail(Handler h) + { + return "</log>" + lineSep; + } +} diff --git a/libjava/classpath/java/util/logging/package.html b/libjava/classpath/java/util/logging/package.html new file mode 100644 index 000000000..31f0494fc --- /dev/null +++ b/libjava/classpath/java/util/logging/package.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- package.html - describes classes in java.util.logging package. + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath 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 +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. --> + +<html> +<head><title>GNU Classpath - java.util.logging</title></head> + +<body> +<p>Utility classes for logging events.</p> + +</body> +</html> |