001 /* FileHandler.java -- a class for publishing log messages to log files
002 Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package java.util.logging;
040
041 import java.io.File;
042 import java.io.FileOutputStream;
043 import java.io.FilterOutputStream;
044 import java.io.IOException;
045 import java.io.OutputStream;
046 import java.util.LinkedList;
047 import java.util.ListIterator;
048
049 /**
050 * A <code>FileHandler</code> publishes log records to a set of log
051 * files. A maximum file size can be specified; as soon as a log file
052 * reaches the size limit, it is closed and the next file in the set
053 * is taken.
054 *
055 * <p><strong>Configuration:</strong> Values of the subsequent
056 * <code>LogManager</code> properties are taken into consideration
057 * when a <code>FileHandler</code> is initialized. If a property is
058 * not defined, or if it has an invalid value, a default is taken
059 * without an exception being thrown.
060 *
061 * <ul>
062 *
063 * <li><code>java.util.FileHandler.level</code> - specifies
064 * the initial severity level threshold. Default value:
065 * <code>Level.ALL</code>.</li>
066 *
067 * <li><code>java.util.FileHandler.filter</code> - specifies
068 * the name of a Filter class. Default value: No Filter.</li>
069 *
070 * <li><code>java.util.FileHandler.formatter</code> - specifies
071 * the name of a Formatter class. Default value:
072 * <code>java.util.logging.XMLFormatter</code>.</li>
073 *
074 * <li><code>java.util.FileHandler.encoding</code> - specifies
075 * the name of the character encoding. Default value:
076 * the default platform encoding.</li>
077 *
078 * <li><code>java.util.FileHandler.limit</code> - specifies the number
079 * of bytes a log file is approximately allowed to reach before it
080 * is closed and the handler switches to the next file in the
081 * rotating set. A value of zero means that files can grow
082 * without limit. Default value: 0 (unlimited growth).</li>
083 *
084 * <li><code>java.util.FileHandler.count</code> - specifies the number
085 * of log files through which this handler cycles. Default value:
086 * 1.</li>
087 *
088 * <li><code>java.util.FileHandler.pattern</code> - specifies a
089 * pattern for the location and name of the produced log files.
090 * See the section on <a href="#filePatterns">file name
091 * patterns</a> for details. Default value:
092 * <code>"%h/java%u.log"</code>.</li>
093 *
094 * <li><code>java.util.FileHandler.append</code> - specifies
095 * whether the handler will append log records to existing
096 * files, or whether the handler will clear log files
097 * upon switching to them. Default value: <code>false</code>,
098 * indicating that files will be cleared.</li>
099 *
100 * </ul>
101 *
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:
106 *
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>
111 *
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>
115 *
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>
119 *
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>
124 *
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.
133 *
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.
139 *
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>
146 *
147 * <li><code>%%</code> - replaced by a single percent sign.</li>
148 * </ul>
149 *
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.
153 *
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.
160 *
161 * <p><em>Examples for the GNU platform:</em>
162 *
163 * <p><ul>
164 *
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>
170 *
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>
178 *
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>
186 *
187 * </ul>
188 *
189 * @author Sascha Brawer (brawer@acm.org)
190 */
191 public class FileHandler
192 extends StreamHandler
193 {
194 /**
195 * A literal that prefixes all file-handler related properties in the
196 * logging.properties file.
197 */
198 private static final String PROPERTY_PREFIX = "java.util.logging.FileHandler";
199 /**
200 * The name of the property to set for specifying a file naming (incl. path)
201 * pattern to use with rotating log files.
202 */
203 private static final String PATTERN_KEY = PROPERTY_PREFIX + ".pattern";
204 /**
205 * The default pattern to use when the <code>PATTERN_KEY</code> property was
206 * not specified in the logging.properties file.
207 */
208 private static final String DEFAULT_PATTERN = "%h/java%u.log";
209 /**
210 * The name of the property to set for specifying an approximate maximum
211 * amount, in bytes, to write to any one log output file. A value of zero
212 * (which is the default) implies a no limit.
213 */
214 private static final String LIMIT_KEY = PROPERTY_PREFIX + ".limit";
215 private static final int DEFAULT_LIMIT = 0;
216 /**
217 * The name of the property to set for specifying how many output files to
218 * cycle through. The default value is 1.
219 */
220 private static final String COUNT_KEY = PROPERTY_PREFIX + ".count";
221 private static final int DEFAULT_COUNT = 1;
222 /**
223 * The name of the property to set for specifying whether this handler should
224 * append, or not, its output to existing files. The default value is
225 * <code>false</code> meaning NOT to append.
226 */
227 private static final String APPEND_KEY = PROPERTY_PREFIX + ".append";
228 private static final boolean DEFAULT_APPEND = false;
229
230 /**
231 * The number of bytes a log file is approximately allowed to reach
232 * before it is closed and the handler switches to the next file in
233 * the rotating set. A value of zero means that files can grow
234 * without limit.
235 */
236 private final int limit;
237
238
239 /**
240 * The number of log files through which this handler cycles.
241 */
242 private final int count;
243
244
245 /**
246 * The pattern for the location and name of the produced log files.
247 * See the section on <a href="#filePatterns">file name patterns</a>
248 * for details.
249 */
250 private final String pattern;
251
252
253 /**
254 * Indicates whether the handler will append log records to existing
255 * files (<code>true</code>), or whether the handler will clear log files
256 * upon switching to them (<code>false</code>).
257 */
258 private final boolean append;
259
260
261 /**
262 * The number of bytes that have currently been written to the stream.
263 * Package private for use in inner classes.
264 */
265 long written;
266
267
268 /**
269 * A linked list of files we are, or have written to. The entries
270 * are file path strings, kept in the order
271 */
272 private LinkedList logFiles;
273
274
275 /**
276 * Constructs a <code>FileHandler</code>, taking all property values
277 * from the current {@link LogManager LogManager} configuration.
278 *
279 * @throws java.io.IOException FIXME: The Sun Javadoc says: "if
280 * there are IO problems opening the files." This conflicts
281 * with the general principle that configuration errors do
282 * not prohibit construction. Needs review.
283 *
284 * @throws SecurityException if a security manager exists and
285 * the caller is not granted the permission to control
286 * the logging infrastructure.
287 */
288 public FileHandler()
289 throws IOException, SecurityException
290 {
291 this(LogManager.getLogManager().getProperty(PATTERN_KEY),
292 LogManager.getIntProperty(LIMIT_KEY, DEFAULT_LIMIT),
293 LogManager.getIntProperty(COUNT_KEY, DEFAULT_COUNT),
294 LogManager.getBooleanProperty(APPEND_KEY, DEFAULT_APPEND));
295 }
296
297
298 /* FIXME: Javadoc missing. */
299 public FileHandler(String pattern)
300 throws IOException, SecurityException
301 {
302 this(pattern, DEFAULT_LIMIT, DEFAULT_COUNT, DEFAULT_APPEND);
303 }
304
305
306 /* FIXME: Javadoc missing. */
307 public FileHandler(String pattern, boolean append)
308 throws IOException, SecurityException
309 {
310 this(pattern, DEFAULT_LIMIT, DEFAULT_COUNT, append);
311 }
312
313
314 /* FIXME: Javadoc missing. */
315 public FileHandler(String pattern, int limit, int count)
316 throws IOException, SecurityException
317 {
318 this(pattern, limit, count,
319 LogManager.getBooleanProperty(APPEND_KEY, DEFAULT_APPEND));
320 }
321
322
323 /**
324 * Constructs a <code>FileHandler</code> given the pattern for the
325 * location and name of the produced log files, the size limit, the
326 * number of log files thorough which the handler will rotate, and
327 * the <code>append</code> property. All other property values are
328 * taken from the current {@link LogManager LogManager}
329 * configuration.
330 *
331 * @param pattern The pattern for the location and name of the
332 * produced log files. See the section on <a
333 * href="#filePatterns">file name patterns</a> for details.
334 * If <code>pattern</code> is <code>null</code>, the value is
335 * taken from the {@link LogManager LogManager} configuration
336 * property
337 * <code>java.util.logging.FileHandler.pattern</code>.
338 * However, this is a pecularity of the GNU implementation,
339 * and Sun's API specification does not mention what behavior
340 * is to be expected for <code>null</code>. Therefore,
341 * applications should not rely on this feature.
342 *
343 * @param limit specifies the number of bytes a log file is
344 * approximately allowed to reach before it is closed and the
345 * handler switches to the next file in the rotating set. A
346 * value of zero means that files can grow without limit.
347 *
348 * @param count specifies the number of log files through which this
349 * handler cycles.
350 *
351 * @param append specifies whether the handler will append log
352 * records to existing files (<code>true</code>), or whether the
353 * handler will clear log files upon switching to them
354 * (<code>false</code>).
355 *
356 * @throws java.io.IOException FIXME: The Sun Javadoc says: "if
357 * there are IO problems opening the files." This conflicts
358 * with the general principle that configuration errors do
359 * not prohibit construction. Needs review.
360 *
361 * @throws SecurityException if a security manager exists and
362 * the caller is not granted the permission to control
363 * the logging infrastructure.
364 * <p>FIXME: This seems in contrast to all other handler
365 * constructors -- verify this by running tests against
366 * the Sun reference implementation.
367 */
368 public FileHandler(String pattern,
369 int limit,
370 int count,
371 boolean append)
372 throws IOException, SecurityException
373 {
374 super(/* output stream, created below */ null,
375 PROPERTY_PREFIX,
376 /* default level */ Level.ALL,
377 /* formatter */ null,
378 /* default formatter */ XMLFormatter.class);
379
380 if ((limit <0) || (count < 1))
381 throw new IllegalArgumentException();
382
383 this.pattern = pattern != null ? pattern : DEFAULT_PATTERN;
384 this.limit = limit;
385 this.count = count;
386 this.append = append;
387 this.written = 0;
388 this.logFiles = new LinkedList ();
389
390 setOutputStream (createFileStream (this.pattern, limit, count, append,
391 /* generation */ 0));
392 }
393
394
395 /* FIXME: Javadoc missing. */
396 private OutputStream createFileStream(String pattern,
397 int limit,
398 int count,
399 boolean append,
400 int generation)
401 {
402 String path;
403 int unique = 0;
404
405 /* Throws a SecurityException if the caller does not have
406 * LoggingPermission("control").
407 */
408 LogManager.getLogManager().checkAccess();
409
410 /* Default value from the java.util.logging.FileHandler.pattern
411 * LogManager configuration property.
412 */
413 if (pattern == null)
414 pattern = LogManager.getLogManager().getProperty(PATTERN_KEY);
415 if (pattern == null)
416 pattern = DEFAULT_PATTERN;
417
418 if (count > 1 && !has (pattern, 'g'))
419 pattern = pattern + ".%g";
420
421 do
422 {
423 path = replaceFileNameEscapes(pattern, generation, unique, count);
424
425 try
426 {
427 File file = new File(path);
428 if (!file.exists () || append)
429 {
430 FileOutputStream fout = new FileOutputStream (file, append);
431 // FIXME we need file locks for this to work properly, but they
432 // are not implemented yet in Classpath! Madness!
433 // FileChannel channel = fout.getChannel ();
434 // FileLock lock = channel.tryLock ();
435 // if (lock != null) // We've locked the file.
436 // {
437 if (logFiles.isEmpty ())
438 logFiles.addFirst (path);
439 return new ostr (fout);
440 // }
441 }
442 }
443 catch (Exception ex)
444 {
445 reportError (null, ex, ErrorManager.OPEN_FAILURE);
446 }
447
448 unique = unique + 1;
449 if (!has (pattern, 'u'))
450 pattern = pattern + ".%u";
451 }
452 while (true);
453 }
454
455
456 /**
457 * Replaces the substrings <code>"/"</code> by the value of the
458 * system property <code>"file.separator"</code>, <code>"%t"</code>
459 * by the value of the system property
460 * <code>"java.io.tmpdir"</code>, <code>"%h"</code> by the value of
461 * the system property <code>"user.home"</code>, <code>"%g"</code>
462 * by the value of <code>generation</code>, <code>"%u"</code> by the
463 * value of <code>uniqueNumber</code>, and <code>"%%"</code> by a
464 * single percent character. If <code>pattern</code> does
465 * <em>not</em> contain the sequence <code>"%g"</code>,
466 * the value of <code>generation</code> will be appended to
467 * the result.
468 *
469 * @throws NullPointerException if one of the system properties
470 * <code>"file.separator"</code>,
471 * <code>"java.io.tmpdir"</code>, or
472 * <code>"user.home"</code> has no value and the
473 * corresponding escape sequence appears in
474 * <code>pattern</code>.
475 */
476 private static String replaceFileNameEscapes(String pattern,
477 int generation,
478 int uniqueNumber,
479 int count)
480 {
481 StringBuffer buf = new StringBuffer(pattern);
482 String replaceWith;
483 boolean foundGeneration = false;
484
485 int pos = 0;
486 do
487 {
488 // Uncomment the next line for finding bugs.
489 // System.out.println(buf.substring(0,pos) + '|' + buf.substring(pos));
490
491 if (buf.charAt(pos) == '/')
492 {
493 /* The same value is also provided by java.io.File.separator. */
494 replaceWith = System.getProperty("file.separator");
495 buf.replace(pos, pos + 1, replaceWith);
496 pos = pos + replaceWith.length() - 1;
497 continue;
498 }
499
500 if (buf.charAt(pos) == '%')
501 {
502 switch (buf.charAt(pos + 1))
503 {
504 case 't':
505 replaceWith = System.getProperty("java.io.tmpdir");
506 break;
507
508 case 'h':
509 replaceWith = System.getProperty("user.home");
510 break;
511
512 case 'g':
513 replaceWith = Integer.toString(generation);
514 foundGeneration = true;
515 break;
516
517 case 'u':
518 replaceWith = Integer.toString(uniqueNumber);
519 break;
520
521 case '%':
522 replaceWith = "%";
523 break;
524
525 default:
526 replaceWith = "??";
527 break; // FIXME: Throw exception?
528 }
529
530 buf.replace(pos, pos + 2, replaceWith);
531 pos = pos + replaceWith.length() - 1;
532 continue;
533 }
534 }
535 while (++pos < buf.length() - 1);
536
537 if (!foundGeneration && (count > 1))
538 {
539 buf.append('.');
540 buf.append(generation);
541 }
542
543 return buf.toString();
544 }
545
546
547 /* FIXME: Javadoc missing. */
548 public void publish(LogRecord record)
549 {
550 if (limit > 0 && written >= limit)
551 rotate ();
552 super.publish(record);
553 flush ();
554 }
555
556 /**
557 * Rotates the current log files, possibly removing one if we
558 * exceed the file count.
559 */
560 private synchronized void rotate ()
561 {
562 if (logFiles.size () > 0)
563 {
564 File f1 = null;
565 ListIterator lit = null;
566
567 // If we reach the file count, ditch the oldest file.
568 if (logFiles.size () == count)
569 {
570 f1 = new File ((String) logFiles.getLast ());
571 f1.delete ();
572 lit = logFiles.listIterator (logFiles.size () - 1);
573 }
574 // Otherwise, move the oldest to a new location.
575 else
576 {
577 String path = replaceFileNameEscapes (pattern, logFiles.size (),
578 /* unique */ 0, count);
579 f1 = new File (path);
580 logFiles.addLast (path);
581 lit = logFiles.listIterator (logFiles.size () - 1);
582 }
583
584 // Now rotate the files.
585 while (lit.hasPrevious ())
586 {
587 String s = (String) lit.previous ();
588 File f2 = new File (s);
589 f2.renameTo (f1);
590 f1 = f2;
591 }
592 }
593
594 setOutputStream (createFileStream (pattern, limit, count, append,
595 /* generation */ 0));
596
597 // Reset written count.
598 written = 0;
599 }
600
601 /**
602 * Tell if <code>pattern</code> contains the pattern sequence
603 * with character <code>escape</code>. That is, if <code>escape</code>
604 * is 'g', this method returns true if the given pattern contains
605 * "%g", and not just the substring "%g" (for example, in the case of
606 * "%%g").
607 *
608 * @param pattern The pattern to test.
609 * @param escape The escape character to search for.
610 * @return True iff the pattern contains the escape sequence with the
611 * given character.
612 */
613 private static boolean has (final String pattern, final char escape)
614 {
615 final int len = pattern.length ();
616 boolean sawPercent = false;
617 for (int i = 0; i < len; i++)
618 {
619 char c = pattern.charAt (i);
620 if (sawPercent)
621 {
622 if (c == escape)
623 return true;
624 if (c == '%') // Double percent
625 {
626 sawPercent = false;
627 continue;
628 }
629 }
630 sawPercent = (c == '%');
631 }
632 return false;
633 }
634
635 /**
636 * An output stream that tracks the number of bytes written to it.
637 */
638 private final class ostr extends FilterOutputStream
639 {
640 private ostr (OutputStream out)
641 {
642 super (out);
643 }
644
645 public void write (final int b) throws IOException
646 {
647 out.write (b);
648 FileHandler.this.written++; // FIXME: synchronize?
649 }
650
651 public void write (final byte[] b) throws IOException
652 {
653 write (b, 0, b.length);
654 }
655
656 public void write (final byte[] b, final int offset, final int length)
657 throws IOException
658 {
659 out.write (b, offset, length);
660 FileHandler.this.written += length; // FIXME: synchronize?
661 }
662 }
663 }