1 /* FileHandler.java -- a class for publishing log messages to log files
2 Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package java
.util
.logging
;
42 import java
.io
.FileOutputStream
;
43 import java
.io
.FilterOutputStream
;
44 import java
.io
.IOException
;
45 import java
.io
.OutputStream
;
46 import java
.util
.LinkedList
;
47 import java
.util
.ListIterator
;
50 * A <code>FileHandler</code> publishes log records to a set of log
51 * files. A maximum file size can be specified; as soon as a log file
52 * reaches the size limit, it is closed and the next file in the set
55 * <p><strong>Configuration:</strong> Values of the subsequent
56 * <code>LogManager</code> properties are taken into consideration
57 * when a <code>FileHandler</code> is initialized. If a property is
58 * not defined, or if it has an invalid value, a default is taken
59 * without an exception being thrown.
63 * <li><code>java.util.FileHandler.level</code> - specifies
64 * the initial severity level threshold. Default value:
65 * <code>Level.ALL</code>.</li>
67 * <li><code>java.util.FileHandler.filter</code> - specifies
68 * the name of a Filter class. Default value: No Filter.</li>
70 * <li><code>java.util.FileHandler.formatter</code> - specifies
71 * the name of a Formatter class. Default value:
72 * <code>java.util.logging.XMLFormatter</code>.</li>
74 * <li><code>java.util.FileHandler.encoding</code> - specifies
75 * the name of the character encoding. Default value:
76 * the default platform encoding.</li>
78 * <li><code>java.util.FileHandler.limit</code> - specifies the number
79 * of bytes a log file is approximately allowed to reach before it
80 * is closed and the handler switches to the next file in the
81 * rotating set. A value of zero means that files can grow
82 * without limit. Default value: 0 (unlimited growth).</li>
84 * <li><code>java.util.FileHandler.count</code> - specifies the number
85 * of log files through which this handler cycles. Default value:
88 * <li><code>java.util.FileHandler.pattern</code> - specifies a
89 * pattern for the location and name of the produced log files.
90 * See the section on <a href="#filePatterns">file name
91 * patterns</a> for details. Default value:
92 * <code>"%h/java%u.log"</code>.</li>
94 * <li><code>java.util.FileHandler.append</code> - specifies
95 * whether the handler will append log records to existing
96 * files, or whether the handler will clear log files
97 * upon switching to them. Default value: <code>false</code>,
98 * indicating that files will be cleared.</li>
102 * <p><a name="filePatterns"><strong>File Name Patterns:</strong></a>
103 * The name and location and log files are specified with pattern
104 * strings. The handler will replace the following character sequences
105 * when opening log files:
108 * <li><code>/</code> - replaced by the platform-specific path name
109 * separator. This value is taken from the system property
110 * <code>file.separator</code>.</li>
112 * <li><code>%t</code> - replaced by the platform-specific location of
113 * the directory intended for temporary files. This value is
114 * taken from the system property <code>java.io.tmpdir</code>.</li>
116 * <li><code>%h</code> - replaced by the location of the home
117 * directory of the current user. This value is taken from the
118 * system property <code>user.home</code>.</li>
120 * <li><code>%g</code> - replaced by a generation number for
121 * distinguisthing the individual items in the rotating set
122 * of log files. The generation number cycles through the
123 * sequence 0, 1, ..., <code>count</code> - 1.</li>
125 * <li><code>%u</code> - replaced by a unique number for
126 * distinguisthing the output files of several concurrently
127 * running processes. The <code>FileHandler</code> starts
128 * with 0 when it tries to open a log file. If the file
129 * cannot be opened because it is currently in use,
130 * the unique number is incremented by one and opening
131 * is tried again. These steps are repeated until the
132 * opening operation succeeds.
134 * <p>FIXME: Is the following correct? Please review. The unique
135 * number is determined for each log file individually when it is
136 * opened upon switching to the next file. Therefore, it is not
137 * correct to assume that all log files in a rotating set bear the
138 * same unique number.
140 * <p>FIXME: The Javadoc for the Sun reference implementation
141 * says: "Note that the use of unique ids to avoid conflicts is
142 * only guaranteed to work reliably when using a local disk file
143 * system." Why? This needs to be mentioned as well, in case
144 * the reviewers decide the statement is true. Otherwise,
145 * file a bug report with Sun.</li>
147 * <li><code>%%</code> - replaced by a single percent sign.</li>
150 * <p>If the pattern string does not contain <code>%g</code> and
151 * <code>count</code> is greater than one, the handler will append
152 * the string <code>.%g</code> to the specified pattern.
154 * <p>If the handler attempts to open a log file, this log file
155 * is being used at the time of the attempt, and the pattern string
156 * does not contain <code>%u</code>, the handler will append
157 * the string <code>.%u</code> to the specified pattern. This
158 * step is performed after any generation number has been
161 * <p><em>Examples for the GNU platform:</em>
165 * <li><code>%h/java%u.log</code> will lead to a single log file
166 * <code>/home/janet/java0.log</code>, assuming <code>count</code>
167 * equals 1, the user's home directory is
168 * <code>/home/janet</code>, and the attempt to open the file
171 * <li><code>%h/java%u.log</code> will lead to three log files
172 * <code>/home/janet/java0.log.0</code>,
173 * <code>/home/janet/java0.log.1</code>, and
174 * <code>/home/janet/java0.log.2</code>,
175 * assuming <code>count</code> equals 3, the user's home
176 * directory is <code>/home/janet</code>, and all attempts
177 * to open files succeed.</li>
179 * <li><code>%h/java%u.log</code> will lead to three log files
180 * <code>/home/janet/java0.log.0</code>,
181 * <code>/home/janet/java1.log.1</code>, and
182 * <code>/home/janet/java0.log.2</code>,
183 * assuming <code>count</code> equals 3, the user's home
184 * directory is <code>/home/janet</code>, and the attempt
185 * to open <code>/home/janet/java0.log.1</code> fails.</li>
189 * @author Sascha Brawer (brawer@acm.org)
191 public class FileHandler
192 extends StreamHandler
195 * The number of bytes a log file is approximately allowed to reach
196 * before it is closed and the handler switches to the next file in
197 * the rotating set. A value of zero means that files can grow
200 private final int limit
;
204 * The number of log files through which this handler cycles.
206 private final int count
;
210 * The pattern for the location and name of the produced log files.
211 * See the section on <a href="#filePatterns">file name patterns</a>
214 private final String pattern
;
218 * Indicates whether the handler will append log records to existing
219 * files (<code>true</code>), or whether the handler will clear log files
220 * upon switching to them (<code>false</code>).
222 private final boolean append
;
226 * The number of bytes that have currently been written to the stream.
227 * Package private for use in inner classes.
233 * A linked list of files we are, or have written to. The entries
234 * are file path strings, kept in the order
236 private LinkedList logFiles
;
240 * Constructs a <code>FileHandler</code>, taking all property values
241 * from the current {@link LogManager LogManager} configuration.
243 * @throws java.io.IOException FIXME: The Sun Javadoc says: "if
244 * there are IO problems opening the files." This conflicts
245 * with the general principle that configuration errors do
246 * not prohibit construction. Needs review.
248 * @throws SecurityException if a security manager exists and
249 * the caller is not granted the permission to control
250 * the logging infrastructure.
253 throws IOException
, SecurityException
255 this(/* pattern: use configiguration */ null,
257 LogManager
.getIntProperty("java.util.logging.FileHandler.limit",
260 LogManager
.getIntProperty("java.util.logging.FileHandler.count",
263 LogManager
.getBooleanProperty("java.util.logging.FileHandler.append",
264 /* default */ false));
268 /* FIXME: Javadoc missing. */
269 public FileHandler(String pattern
)
270 throws IOException
, SecurityException
279 /* FIXME: Javadoc missing. */
280 public FileHandler(String pattern
, boolean append
)
281 throws IOException
, SecurityException
290 /* FIXME: Javadoc missing. */
291 public FileHandler(String pattern
, int limit
, int count
)
292 throws IOException
, SecurityException
294 this(pattern
, limit
, count
,
295 LogManager
.getBooleanProperty(
296 "java.util.logging.FileHandler.append",
297 /* default */ false));
302 * Constructs a <code>FileHandler</code> given the pattern for the
303 * location and name of the produced log files, the size limit, the
304 * number of log files thorough which the handler will rotate, and
305 * the <code>append</code> property. All other property values are
306 * taken from the current {@link LogManager LogManager}
309 * @param pattern The pattern for the location and name of the
310 * produced log files. See the section on <a
311 * href="#filePatterns">file name patterns</a> for details.
312 * If <code>pattern</code> is <code>null</code>, the value is
313 * taken from the {@link LogManager LogManager} configuration
315 * <code>java.util.logging.FileHandler.pattern</code>.
316 * However, this is a pecularity of the GNU implementation,
317 * and Sun's API specification does not mention what behavior
318 * is to be expected for <code>null</code>. Therefore,
319 * applications should not rely on this feature.
321 * @param limit specifies the number of bytes a log file is
322 * approximately allowed to reach before it is closed and the
323 * handler switches to the next file in the rotating set. A
324 * value of zero means that files can grow without limit.
326 * @param count specifies the number of log files through which this
329 * @param append specifies whether the handler will append log
330 * records to existing files (<code>true</code>), or whether the
331 * handler will clear log files upon switching to them
332 * (<code>false</code>).
334 * @throws java.io.IOException FIXME: The Sun Javadoc says: "if
335 * there are IO problems opening the files." This conflicts
336 * with the general principle that configuration errors do
337 * not prohibit construction. Needs review.
339 * @throws SecurityException if a security manager exists and
340 * the caller is not granted the permission to control
341 * the logging infrastructure.
342 * <p>FIXME: This seems in contrast to all other handler
343 * constructors -- verify this by running tests against
344 * the Sun reference implementation.
346 public FileHandler(String pattern
,
350 throws IOException
, SecurityException
352 super(/* output stream, created below */ null,
353 "java.util.logging.FileHandler",
354 /* default level */ Level
.ALL
,
355 /* formatter */ null,
356 /* default formatter */ XMLFormatter
.class);
358 if ((limit
<0) || (count
< 1))
359 throw new IllegalArgumentException();
361 this.pattern
= pattern
;
364 this.append
= append
;
366 this.logFiles
= new LinkedList ();
368 setOutputStream (createFileStream (pattern
, limit
, count
, append
,
369 /* generation */ 0));
373 /* FIXME: Javadoc missing. */
374 private OutputStream
createFileStream(String pattern
,
383 /* Throws a SecurityException if the caller does not have
384 * LoggingPermission("control").
386 LogManager
.getLogManager().checkAccess();
388 /* Default value from the java.util.logging.FileHandler.pattern
389 * LogManager configuration property.
392 pattern
= LogManager
.getLogManager().getProperty(
393 "java.util.logging.FileHandler.pattern");
395 pattern
= "%h/java%u.log";
397 if (count
> 1 && !has (pattern
, 'g'))
398 pattern
= pattern
+ ".%g";
402 path
= replaceFileNameEscapes(pattern
, generation
, unique
, count
);
406 File file
= new File(path
);
407 if (!file
.exists () || append
)
409 FileOutputStream fout
= new FileOutputStream (file
, append
);
410 // FIXME we need file locks for this to work properly, but they
411 // are not implemented yet in Classpath! Madness!
412 // FileChannel channel = fout.getChannel ();
413 // FileLock lock = channel.tryLock ();
414 // if (lock != null) // We've locked the file.
416 if (logFiles
.isEmpty ())
417 logFiles
.addFirst (path
);
418 return new ostr (fout
);
424 reportError (null, ex
, ErrorManager
.OPEN_FAILURE
);
428 if (!has (pattern
, 'u'))
429 pattern
= pattern
+ ".%u";
436 * Replaces the substrings <code>"/"</code> by the value of the
437 * system property <code>"file.separator"</code>, <code>"%t"</code>
438 * by the value of the system property
439 * <code>"java.io.tmpdir"</code>, <code>"%h"</code> by the value of
440 * the system property <code>"user.home"</code>, <code>"%g"</code>
441 * by the value of <code>generation</code>, <code>"%u"</code> by the
442 * value of <code>uniqueNumber</code>, and <code>"%%"</code> by a
443 * single percent character. If <code>pattern</code> does
444 * <em>not</em> contain the sequence <code>"%g"</code>,
445 * the value of <code>generation</code> will be appended to
448 * @throws NullPointerException if one of the system properties
449 * <code>"file.separator"</code>,
450 * <code>"java.io.tmpdir"</code>, or
451 * <code>"user.home"</code> has no value and the
452 * corresponding escape sequence appears in
453 * <code>pattern</code>.
455 private static String
replaceFileNameEscapes(String pattern
,
460 StringBuffer buf
= new StringBuffer(pattern
);
462 boolean foundGeneration
= false;
467 // Uncomment the next line for finding bugs.
468 // System.out.println(buf.substring(0,pos) + '|' + buf.substring(pos));
470 if (buf
.charAt(pos
) == '/')
472 /* The same value is also provided by java.io.File.separator. */
473 replaceWith
= System
.getProperty("file.separator");
474 buf
.replace(pos
, pos
+ 1, replaceWith
);
475 pos
= pos
+ replaceWith
.length() - 1;
479 if (buf
.charAt(pos
) == '%')
481 switch (buf
.charAt(pos
+ 1))
484 replaceWith
= System
.getProperty("java.io.tmpdir");
488 replaceWith
= System
.getProperty("user.home");
492 replaceWith
= Integer
.toString(generation
);
493 foundGeneration
= true;
497 replaceWith
= Integer
.toString(uniqueNumber
);
506 break; // FIXME: Throw exception?
509 buf
.replace(pos
, pos
+ 2, replaceWith
);
510 pos
= pos
+ replaceWith
.length() - 1;
514 while (++pos
< buf
.length() - 1);
516 if (!foundGeneration
&& (count
> 1))
519 buf
.append(generation
);
522 return buf
.toString();
526 /* FIXME: Javadoc missing. */
527 public void publish(LogRecord record
)
529 if (limit
> 0 && written
>= limit
)
531 super.publish(record
);
536 * Rotates the current log files, possibly removing one if we
537 * exceed the file count.
539 private synchronized void rotate ()
541 if (logFiles
.size () > 0)
544 ListIterator lit
= null;
546 // If we reach the file count, ditch the oldest file.
547 if (logFiles
.size () == count
)
549 f1
= new File ((String
) logFiles
.getLast ());
551 lit
= logFiles
.listIterator (logFiles
.size () - 1);
553 // Otherwise, move the oldest to a new location.
556 String path
= replaceFileNameEscapes (pattern
, logFiles
.size (),
557 /* unique */ 0, count
);
558 f1
= new File (path
);
559 logFiles
.addLast (path
);
560 lit
= logFiles
.listIterator (logFiles
.size () - 1);
563 // Now rotate the files.
564 while (lit
.hasPrevious ())
566 String s
= (String
) lit
.previous ();
567 File f2
= new File (s
);
573 setOutputStream (createFileStream (pattern
, limit
, count
, append
,
574 /* generation */ 0));
576 // Reset written count.
581 * Tell if <code>pattern</code> contains the pattern sequence
582 * with character <code>escape</code>. That is, if <code>escape</code>
583 * is 'g', this method returns true if the given pattern contains
584 * "%g", and not just the substring "%g" (for example, in the case of
587 * @param pattern The pattern to test.
588 * @param escape The escape character to search for.
589 * @return True iff the pattern contains the escape sequence with the
592 private static boolean has (final String pattern
, final char escape
)
594 final int len
= pattern
.length ();
595 boolean sawPercent
= false;
596 for (int i
= 0; i
< len
; i
++)
598 char c
= pattern
.charAt (i
);
603 if (c
== '%') // Double percent
609 sawPercent
= (c
== '%');
615 * An output stream that tracks the number of bytes written to it.
617 private final class ostr
extends FilterOutputStream
619 private ostr (OutputStream out
)
624 public void write (final int b
) throws IOException
627 FileHandler
.this.written
++; // FIXME: synchronize?
630 public void write (final byte[] b
) throws IOException
632 write (b
, 0, b
.length
);
635 public void write (final byte[] b
, final int offset
, final int length
)
638 out
.write (b
, offset
, length
);
639 FileHandler
.this.written
+= length
; // FIXME: synchronize?