| /* 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 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) |
| { |
| StringBuffer buf = new StringBuffer(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? |
| } |
| } |
| } |