123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 |
- /* 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?
- }
- }
- }
|