| /* 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., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 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.IOException; |
| |
| /** |
| * 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>file.separator</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 |
| { |
| /** |
| * 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; |
| |
| |
| /** |
| * 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(/* pattern: use configiguration */ null, |
| |
| LogManager.getIntProperty("java.util.logging.FileHandler.limit", |
| /* default */ 0), |
| |
| LogManager.getIntProperty("java.util.logging.FileHandler.count", |
| /* default */ 1), |
| |
| LogManager.getBooleanProperty("java.util.logging.FileHandler.append", |
| /* default */ false)); |
| } |
| |
| |
| /* FIXME: Javadoc missing. */ |
| public FileHandler(String pattern) |
| throws IOException, SecurityException |
| { |
| this(pattern, |
| /* limit */ 0, |
| /* count */ 1, |
| /* append */ false); |
| } |
| |
| |
| /* FIXME: Javadoc missing. */ |
| public FileHandler(String pattern, boolean append) |
| throws IOException, SecurityException |
| { |
| this(pattern, |
| /* limit */ 0, |
| /* count */ 1, |
| append); |
| } |
| |
| |
| /* FIXME: Javadoc missing. */ |
| public FileHandler(String pattern, int limit, int count) |
| throws IOException, SecurityException |
| { |
| this(pattern, limit, count, |
| LogManager.getBooleanProperty( |
| "java.util.logging.FileHandler.append", |
| /* default */ false)); |
| } |
| |
| |
| /** |
| * 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(createFileStream(pattern, limit, count, append, |
| /* generation */ 0), |
| "java.util.logging.FileHandler", |
| /* default level */ Level.ALL, |
| /* formatter */ null, |
| /* default formatter */ XMLFormatter.class); |
| |
| if ((limit <0) || (count < 1)) |
| throw new IllegalArgumentException(); |
| |
| this.pattern = pattern; |
| this.limit = limit; |
| this.count = count; |
| this.append = append; |
| } |
| |
| |
| /* FIXME: Javadoc missing. */ |
| private static java.io.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( |
| "java.util.logging.FileHandler.pattern"); |
| if (pattern == null) |
| pattern = "%h/java%u.log"; |
| |
| do |
| { |
| path = replaceFileNameEscapes(pattern, generation, unique, count); |
| |
| try |
| { |
| File file = new File(path); |
| if (file.createNewFile()) |
| return new FileOutputStream(path, append); |
| } |
| catch (Exception ex) |
| { |
| ex.printStackTrace(); |
| } |
| |
| unique = unique + 1; |
| if (pattern.indexOf("%u") < 0) |
| 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, implementation incomplete. */ |
| public void publish(LogRecord record) |
| { |
| super.publish(record); |
| |
| /* FIXME: Decide when to switch over. How do we get to |
| * the number of bytes published so far? Two possibilities: |
| * 1. File.length, 2. have metering wrapper around |
| * output stream counting the number of written bytes. |
| */ |
| |
| /* FIXME: Switch over if needed! This implementation always |
| * writes into a single file, i.e. behaves as if limit |
| * always was zero. So, the implementation is somewhat |
| * functional but incomplete. |
| */ |
| } |
| } |