Merge from mainline
[official-gcc.git] / libjava / classpath / java / util / logging / FileHandler.java
blobb03df97ec605ba73b6fa401f4a62f3bb9ab720ac
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)
9 any later version.
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
19 02110-1301 USA.
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
24 combination.
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;
41 import java.io.File;
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;
49 /**
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
53 * is taken.
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.
61 * <ul>
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:
86 * 1.</li>
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>
100 * </ul>
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:
107 * <p><ul>
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>file.separator</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>
148 * </ul>
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
159 * appended.
161 * <p><em>Examples for the GNU platform:</em>
163 * <p><ul>
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
169 * succeeds.</li>
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>
187 * </ul>
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
198 * without limit.
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>
212 * for details.
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.
229 long written;
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.
252 public FileHandler()
253 throws IOException, SecurityException
255 this(/* pattern: use configiguration */ null,
257 LogManager.getIntProperty("java.util.logging.FileHandler.limit",
258 /* default */ 0),
260 LogManager.getIntProperty("java.util.logging.FileHandler.count",
261 /* default */ 1),
263 LogManager.getBooleanProperty("java.util.logging.FileHandler.append",
264 /* default */ false));
268 /* FIXME: Javadoc missing. */
269 public FileHandler(String pattern)
270 throws IOException, SecurityException
272 this(pattern,
273 /* limit */ 0,
274 /* count */ 1,
275 /* append */ false);
279 /* FIXME: Javadoc missing. */
280 public FileHandler(String pattern, boolean append)
281 throws IOException, SecurityException
283 this(pattern,
284 /* limit */ 0,
285 /* count */ 1,
286 append);
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}
307 * configuration.
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
314 * property
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
327 * handler cycles.
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,
347 int limit,
348 int count,
349 boolean append)
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;
362 this.limit = limit;
363 this.count = count;
364 this.append = append;
365 this.written = 0;
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,
375 int limit,
376 int count,
377 boolean append,
378 int generation)
380 String path;
381 int unique = 0;
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.
391 if (pattern == null)
392 pattern = LogManager.getLogManager().getProperty(
393 "java.util.logging.FileHandler.pattern");
394 if (pattern == null)
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.
415 // {
416 if (logFiles.isEmpty ())
417 logFiles.addFirst (path);
418 return new ostr (fout);
419 // }
422 catch (Exception ex)
424 reportError (null, ex, ErrorManager.OPEN_FAILURE);
427 unique = unique + 1;
428 if (!has (pattern, 'u'))
429 pattern = pattern + ".%u";
431 while (true);
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
446 * the result.
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,
456 int generation,
457 int uniqueNumber,
458 int count)
460 StringBuffer buf = new StringBuffer(pattern);
461 String replaceWith;
462 boolean foundGeneration = false;
464 int pos = 0;
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;
476 continue;
479 if (buf.charAt(pos) == '%')
481 switch (buf.charAt(pos + 1))
483 case 't':
484 replaceWith = System.getProperty("java.io.tmpdir");
485 break;
487 case 'h':
488 replaceWith = System.getProperty("user.home");
489 break;
491 case 'g':
492 replaceWith = Integer.toString(generation);
493 foundGeneration = true;
494 break;
496 case 'u':
497 replaceWith = Integer.toString(uniqueNumber);
498 break;
500 case '%':
501 replaceWith = "%";
502 break;
504 default:
505 replaceWith = "??";
506 break; // FIXME: Throw exception?
509 buf.replace(pos, pos + 2, replaceWith);
510 pos = pos + replaceWith.length() - 1;
511 continue;
514 while (++pos < buf.length() - 1);
516 if (!foundGeneration && (count > 1))
518 buf.append('.');
519 buf.append(generation);
522 return buf.toString();
526 /* FIXME: Javadoc missing. */
527 public void publish(LogRecord record)
529 if (limit > 0 && written >= limit)
530 rotate ();
531 super.publish(record);
532 flush ();
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)
543 File f1 = null;
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 ());
550 f1.delete ();
551 lit = logFiles.listIterator (logFiles.size () - 1);
553 // Otherwise, move the oldest to a new location.
554 else
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);
568 f2.renameTo (f1);
569 f1 = f2;
573 setOutputStream (createFileStream (pattern, limit, count, append,
574 /* generation */ 0));
576 // Reset written count.
577 written = 0;
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
585 * "%%g").
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
590 * given character.
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);
599 if (sawPercent)
601 if (c == escape)
602 return true;
603 if (c == '%') // Double percent
605 sawPercent = false;
606 continue;
609 sawPercent = (c == '%');
611 return false;
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)
621 super (out);
624 public void write (final int b) throws IOException
626 out.write (b);
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)
636 throws IOException
638 out.write (b, offset, length);
639 FileHandler.this.written += length; // FIXME: synchronize?