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 gnu.java.lang.CPStringBuilder;
042
043 import java.io.File;
044 import java.io.FileOutputStream;
045 import java.io.FilterOutputStream;
046 import java.io.IOException;
047 import java.io.OutputStream;
048 import java.util.LinkedList;
049 import java.util.ListIterator;
050
051 /**
052 * A <code>FileHandler</code> publishes log records to a set of log
053 * files. A maximum file size can be specified; as soon as a log file
054 * reaches the size limit, it is closed and the next file in the set
055 * is taken.
056 *
057 * <p><strong>Configuration:</strong> Values of the subsequent
058 * <code>LogManager</code> properties are taken into consideration
059 * when a <code>FileHandler</code> is initialized. If a property is
060 * not defined, or if it has an invalid value, a default is taken
061 * without an exception being thrown.
062 *
063 * <ul>
064 *
065 * <li><code>java.util.FileHandler.level</code> - specifies
066 * the initial severity level threshold. Default value:
067 * <code>Level.ALL</code>.</li>
068 *
069 * <li><code>java.util.FileHandler.filter</code> - specifies
070 * the name of a Filter class. Default value: No Filter.</li>
071 *
072 * <li><code>java.util.FileHandler.formatter</code> - specifies
073 * the name of a Formatter class. Default value:
074 * <code>java.util.logging.XMLFormatter</code>.</li>
075 *
076 * <li><code>java.util.FileHandler.encoding</code> - specifies
077 * the name of the character encoding. Default value:
078 * the default platform encoding.</li>
079 *
080 * <li><code>java.util.FileHandler.limit</code> - specifies the number
081 * of bytes a log file is approximately allowed to reach before it
082 * is closed and the handler switches to the next file in the
083 * rotating set. A value of zero means that files can grow
084 * without limit. Default value: 0 (unlimited growth).</li>
085 *
086 * <li><code>java.util.FileHandler.count</code> - specifies the number
087 * of log files through which this handler cycles. Default value:
088 * 1.</li>
089 *
090 * <li><code>java.util.FileHandler.pattern</code> - specifies a
091 * pattern for the location and name of the produced log files.
092 * See the section on <a href="#filePatterns">file name
093 * patterns</a> for details. Default value:
094 * <code>"%h/java%u.log"</code>.</li>
095 *
096 * <li><code>java.util.FileHandler.append</code> - specifies
097 * whether the handler will append log records to existing
098 * files, or whether the handler will clear log files
099 * upon switching to them. Default value: <code>false</code>,
100 * indicating that files will be cleared.</li>
101 *
102 * </ul>
103 *
104 * <p><a name="filePatterns"><strong>File Name Patterns:</strong></a>
105 * The name and location and log files are specified with pattern
106 * strings. The handler will replace the following character sequences
107 * when opening log files:
108 *
109 * <p><ul>
110 * <li><code>/</code> - replaced by the platform-specific path name
111 * separator. This value is taken from the system property
112 * <code>file.separator</code>.</li>
113 *
114 * <li><code>%t</code> - replaced by the platform-specific location of
115 * the directory intended for temporary files. This value is
116 * taken from the system property <code>java.io.tmpdir</code>.</li>
117 *
118 * <li><code>%h</code> - replaced by the location of the home
119 * directory of the current user. This value is taken from the
120 * system property <code>user.home</code>.</li>
121 *
122 * <li><code>%g</code> - replaced by a generation number for
123 * distinguisthing the individual items in the rotating set
124 * of log files. The generation number cycles through the
125 * sequence 0, 1, ..., <code>count</code> - 1.</li>
126 *
127 * <li><code>%u</code> - replaced by a unique number for
128 * distinguisthing the output files of several concurrently
129 * running processes. The <code>FileHandler</code> starts
130 * with 0 when it tries to open a log file. If the file
131 * cannot be opened because it is currently in use,
132 * the unique number is incremented by one and opening
133 * is tried again. These steps are repeated until the
134 * opening operation succeeds.
135 *
136 * <p>FIXME: Is the following correct? Please review. The unique
137 * number is determined for each log file individually when it is
138 * opened upon switching to the next file. Therefore, it is not
139 * correct to assume that all log files in a rotating set bear the
140 * same unique number.
141 *
142 * <p>FIXME: The Javadoc for the Sun reference implementation
143 * says: "Note that the use of unique ids to avoid conflicts is
144 * only guaranteed to work reliably when using a local disk file
145 * system." Why? This needs to be mentioned as well, in case
146 * the reviewers decide the statement is true. Otherwise,
147 * file a bug report with Sun.</li>
148 *
149 * <li><code>%%</code> - replaced by a single percent sign.</li>
150 * </ul>
151 *
152 * <p>If the pattern string does not contain <code>%g</code> and
153 * <code>count</code> is greater than one, the handler will append
154 * the string <code>.%g</code> to the specified pattern.
155 *
156 * <p>If the handler attempts to open a log file, this log file
157 * is being used at the time of the attempt, and the pattern string
158 * does not contain <code>%u</code>, the handler will append
159 * the string <code>.%u</code> to the specified pattern. This
160 * step is performed after any generation number has been
161 * appended.
162 *
163 * <p><em>Examples for the GNU platform:</em>
164 *
165 * <p><ul>
166 *
167 * <li><code>%h/java%u.log</code> will lead to a single log file
168 * <code>/home/janet/java0.log</code>, assuming <code>count</code>
169 * equals 1, the user's home directory is
170 * <code>/home/janet</code>, and the attempt to open the file
171 * succeeds.</li>
172 *
173 * <li><code>%h/java%u.log</code> will lead to three log files
174 * <code>/home/janet/java0.log.0</code>,
175 * <code>/home/janet/java0.log.1</code>, and
176 * <code>/home/janet/java0.log.2</code>,
177 * assuming <code>count</code> equals 3, the user's home
178 * directory is <code>/home/janet</code>, and all attempts
179 * to open files succeed.</li>
180 *
181 * <li><code>%h/java%u.log</code> will lead to three log files
182 * <code>/home/janet/java0.log.0</code>,
183 * <code>/home/janet/java1.log.1</code>, and
184 * <code>/home/janet/java0.log.2</code>,
185 * assuming <code>count</code> equals 3, the user's home
186 * directory is <code>/home/janet</code>, and the attempt
187 * to open <code>/home/janet/java0.log.1</code> fails.</li>
188 *
189 * </ul>
190 *
191 * @author Sascha Brawer (brawer@acm.org)
192 */
193 public class FileHandler
194 extends StreamHandler
195 {
196 /**
197 * A literal that prefixes all file-handler related properties in the
198 * logging.properties file.
199 */
200 private static final String PROPERTY_PREFIX = "java.util.logging.FileHandler";
201 /**
202 * The name of the property to set for specifying a file naming (incl. path)
203 * pattern to use with rotating log files.
204 */
205 private static final String PATTERN_KEY = PROPERTY_PREFIX + ".pattern";
206 /**
207 * The default pattern to use when the <code>PATTERN_KEY</code> property was
208 * not specified in the logging.properties file.
209 */
210 private static final String DEFAULT_PATTERN = "%h/java%u.log";
211 /**
212 * The name of the property to set for specifying an approximate maximum
213 * amount, in bytes, to write to any one log output file. A value of zero
214 * (which is the default) implies a no limit.
215 */
216 private static final String LIMIT_KEY = PROPERTY_PREFIX + ".limit";
217 private static final int DEFAULT_LIMIT = 0;
218 /**
219 * The name of the property to set for specifying how many output files to
220 * cycle through. The default value is 1.
221 */
222 private static final String COUNT_KEY = PROPERTY_PREFIX + ".count";
223 private static final int DEFAULT_COUNT = 1;
224 /**
225 * The name of the property to set for specifying whether this handler should
226 * append, or not, its output to existing files. The default value is
227 * <code>false</code> meaning NOT to append.
228 */
229 private static final String APPEND_KEY = PROPERTY_PREFIX + ".append";
230 private static final boolean DEFAULT_APPEND = false;
231
232 /**
233 * The number of bytes a log file is approximately allowed to reach
234 * before it is closed and the handler switches to the next file in
235 * the rotating set. A value of zero means that files can grow
236 * without limit.
237 */
238 private final int limit;
239
240
241 /**
242 * The number of log files through which this handler cycles.
243 */
244 private final int count;
245
246
247 /**
248 * The pattern for the location and name of the produced log files.
249 * See the section on <a href="#filePatterns">file name patterns</a>
250 * for details.
251 */
252 private final String pattern;
253
254
255 /**
256 * Indicates whether the handler will append log records to existing
257 * files (<code>true</code>), or whether the handler will clear log files
258 * upon switching to them (<code>false</code>).
259 */
260 private final boolean append;
261
262
263 /**
264 * The number of bytes that have currently been written to the stream.
265 * Package private for use in inner classes.
266 */
267 long written;
268
269
270 /**
271 * A linked list of files we are, or have written to. The entries
272 * are file path strings, kept in the order
273 */
274 private LinkedList logFiles;
275
276
277 /**
278 * Constructs a <code>FileHandler</code>, taking all property values
279 * from the current {@link LogManager LogManager} configuration.
280 *
281 * @throws java.io.IOException FIXME: The Sun Javadoc says: "if
282 * there are IO problems opening the files." This conflicts
283 * with the general principle that configuration errors do
284 * not prohibit construction. Needs review.
285 *
286 * @throws SecurityException if a security manager exists and
287 * the caller is not granted the permission to control
288 * the logging infrastructure.
289 */
290 public FileHandler()
291 throws IOException, SecurityException
292 {
293 this(LogManager.getLogManager().getProperty(PATTERN_KEY),
294 LogManager.getIntProperty(LIMIT_KEY, DEFAULT_LIMIT),
295 LogManager.getIntProperty(COUNT_KEY, DEFAULT_COUNT),
296 LogManager.getBooleanProperty(APPEND_KEY, DEFAULT_APPEND));
297 }
298
299
300 /* FIXME: Javadoc missing. */
301 public FileHandler(String pattern)
302 throws IOException, SecurityException
303 {
304 this(pattern, DEFAULT_LIMIT, DEFAULT_COUNT, DEFAULT_APPEND);
305 }
306
307
308 /* FIXME: Javadoc missing. */
309 public FileHandler(String pattern, boolean append)
310 throws IOException, SecurityException
311 {
312 this(pattern, DEFAULT_LIMIT, DEFAULT_COUNT, append);
313 }
314
315
316 /* FIXME: Javadoc missing. */
317 public FileHandler(String pattern, int limit, int count)
318 throws IOException, SecurityException
319 {
320 this(pattern, limit, count,
321 LogManager.getBooleanProperty(APPEND_KEY, DEFAULT_APPEND));
322 }
323
324
325 /**
326 * Constructs a <code>FileHandler</code> given the pattern for the
327 * location and name of the produced log files, the size limit, the
328 * number of log files thorough which the handler will rotate, and
329 * the <code>append</code> property. All other property values are
330 * taken from the current {@link LogManager LogManager}
331 * configuration.
332 *
333 * @param pattern The pattern for the location and name of the
334 * produced log files. See the section on <a
335 * href="#filePatterns">file name patterns</a> for details.
336 * If <code>pattern</code> is <code>null</code>, the value is
337 * taken from the {@link LogManager LogManager} configuration
338 * property
339 * <code>java.util.logging.FileHandler.pattern</code>.
340 * However, this is a pecularity of the GNU implementation,
341 * and Sun's API specification does not mention what behavior
342 * is to be expected for <code>null</code>. Therefore,
343 * applications should not rely on this feature.
344 *
345 * @param limit specifies the number of bytes a log file is
346 * approximately allowed to reach before it is closed and the
347 * handler switches to the next file in the rotating set. A
348 * value of zero means that files can grow without limit.
349 *
350 * @param count specifies the number of log files through which this
351 * handler cycles.
352 *
353 * @param append specifies whether the handler will append log
354 * records to existing files (<code>true</code>), or whether the
355 * handler will clear log files upon switching to them
356 * (<code>false</code>).
357 *
358 * @throws java.io.IOException FIXME: The Sun Javadoc says: "if
359 * there are IO problems opening the files." This conflicts
360 * with the general principle that configuration errors do
361 * not prohibit construction. Needs review.
362 *
363 * @throws SecurityException if a security manager exists and
364 * the caller is not granted the permission to control
365 * the logging infrastructure.
366 * <p>FIXME: This seems in contrast to all other handler
367 * constructors -- verify this by running tests against
368 * the Sun reference implementation.
369 */
370 public FileHandler(String pattern,
371 int limit,
372 int count,
373 boolean append)
374 throws IOException, SecurityException
375 {
376 super(/* output stream, created below */ null,
377 PROPERTY_PREFIX,
378 /* default level */ Level.ALL,
379 /* formatter */ null,
380 /* default formatter */ XMLFormatter.class);
381
382 if ((limit <0) || (count < 1))
383 throw new IllegalArgumentException();
384
385 this.pattern = pattern != null ? pattern : DEFAULT_PATTERN;
386 this.limit = limit;
387 this.count = count;
388 this.append = append;
389 this.written = 0;
390 this.logFiles = new LinkedList ();
391
392 setOutputStream (createFileStream (this.pattern, limit, count, append,
393 /* generation */ 0));
394 }
395
396
397 /* FIXME: Javadoc missing. */
398 private OutputStream createFileStream(String pattern,
399 int limit,
400 int count,
401 boolean append,
402 int generation)
403 {
404 String path;
405 int unique = 0;
406
407 /* Throws a SecurityException if the caller does not have
408 * LoggingPermission("control").
409 */
410 LogManager.getLogManager().checkAccess();
411
412 /* Default value from the java.util.logging.FileHandler.pattern
413 * LogManager configuration property.
414 */
415 if (pattern == null)
416 pattern = LogManager.getLogManager().getProperty(PATTERN_KEY);
417 if (pattern == null)
418 pattern = DEFAULT_PATTERN;
419
420 if (count > 1 && !has (pattern, 'g'))
421 pattern = pattern + ".%g";
422
423 do
424 {
425 path = replaceFileNameEscapes(pattern, generation, unique, count);
426
427 try
428 {
429 File file = new File(path);
430 if (!file.exists () || append)
431 {
432 FileOutputStream fout = new FileOutputStream (file, append);
433 // FIXME we need file locks for this to work properly, but they
434 // are not implemented yet in Classpath! Madness!
435 // FileChannel channel = fout.getChannel ();
436 // FileLock lock = channel.tryLock ();
437 // if (lock != null) // We've locked the file.
438 // {
439 if (logFiles.isEmpty ())
440 logFiles.addFirst (path);
441 return new ostr (fout);
442 // }
443 }
444 }
445 catch (Exception ex)
446 {
447 reportError (null, ex, ErrorManager.OPEN_FAILURE);
448 }
449
450 unique = unique + 1;
451 if (!has (pattern, 'u'))
452 pattern = pattern + ".%u";
453 }
454 while (true);
455 }
456
457
458 /**
459 * Replaces the substrings <code>"/"</code> by the value of the
460 * system property <code>"file.separator"</code>, <code>"%t"</code>
461 * by the value of the system property
462 * <code>"java.io.tmpdir"</code>, <code>"%h"</code> by the value of
463 * the system property <code>"user.home"</code>, <code>"%g"</code>
464 * by the value of <code>generation</code>, <code>"%u"</code> by the
465 * value of <code>uniqueNumber</code>, and <code>"%%"</code> by a
466 * single percent character. If <code>pattern</code> does
467 * <em>not</em> contain the sequence <code>"%g"</code>,
468 * the value of <code>generation</code> will be appended to
469 * the result.
470 *
471 * @throws NullPointerException if one of the system properties
472 * <code>"file.separator"</code>,
473 * <code>"java.io.tmpdir"</code>, or
474 * <code>"user.home"</code> has no value and the
475 * corresponding escape sequence appears in
476 * <code>pattern</code>.
477 */
478 private static String replaceFileNameEscapes(String pattern,
479 int generation,
480 int uniqueNumber,
481 int count)
482 {
483 CPStringBuilder buf = new CPStringBuilder(pattern);
484 String replaceWith;
485 boolean foundGeneration = false;
486
487 int pos = 0;
488 do
489 {
490 // Uncomment the next line for finding bugs.
491 // System.out.println(buf.substring(0,pos) + '|' + buf.substring(pos));
492
493 if (buf.charAt(pos) == '/')
494 {
495 /* The same value is also provided by java.io.File.separator. */
496 replaceWith = System.getProperty("file.separator");
497 buf.replace(pos, pos + 1, replaceWith);
498 pos = pos + replaceWith.length() - 1;
499 continue;
500 }
501
502 if (buf.charAt(pos) == '%')
503 {
504 switch (buf.charAt(pos + 1))
505 {
506 case 't':
507 replaceWith = System.getProperty("java.io.tmpdir");
508 break;
509
510 case 'h':
511 replaceWith = System.getProperty("user.home");
512 break;
513
514 case 'g':
515 replaceWith = Integer.toString(generation);
516 foundGeneration = true;
517 break;
518
519 case 'u':
520 replaceWith = Integer.toString(uniqueNumber);
521 break;
522
523 case '%':
524 replaceWith = "%";
525 break;
526
527 default:
528 replaceWith = "??";
529 break; // FIXME: Throw exception?
530 }
531
532 buf.replace(pos, pos + 2, replaceWith);
533 pos = pos + replaceWith.length() - 1;
534 continue;
535 }
536 }
537 while (++pos < buf.length() - 1);
538
539 if (!foundGeneration && (count > 1))
540 {
541 buf.append('.');
542 buf.append(generation);
543 }
544
545 return buf.toString();
546 }
547
548
549 /* FIXME: Javadoc missing. */
550 public void publish(LogRecord record)
551 {
552 if (limit > 0 && written >= limit)
553 rotate ();
554 super.publish(record);
555 flush ();
556 }
557
558 /**
559 * Rotates the current log files, possibly removing one if we
560 * exceed the file count.
561 */
562 private synchronized void rotate ()
563 {
564 if (logFiles.size () > 0)
565 {
566 File f1 = null;
567 ListIterator lit = null;
568
569 // If we reach the file count, ditch the oldest file.
570 if (logFiles.size () == count)
571 {
572 f1 = new File ((String) logFiles.getLast ());
573 f1.delete ();
574 lit = logFiles.listIterator (logFiles.size () - 1);
575 }
576 // Otherwise, move the oldest to a new location.
577 else
578 {
579 String path = replaceFileNameEscapes (pattern, logFiles.size (),
580 /* unique */ 0, count);
581 f1 = new File (path);
582 logFiles.addLast (path);
583 lit = logFiles.listIterator (logFiles.size () - 1);
584 }
585
586 // Now rotate the files.
587 while (lit.hasPrevious ())
588 {
589 String s = (String) lit.previous ();
590 File f2 = new File (s);
591 f2.renameTo (f1);
592 f1 = f2;
593 }
594 }
595
596 setOutputStream (createFileStream (pattern, limit, count, append,
597 /* generation */ 0));
598
599 // Reset written count.
600 written = 0;
601 }
602
603 /**
604 * Tell if <code>pattern</code> contains the pattern sequence
605 * with character <code>escape</code>. That is, if <code>escape</code>
606 * is 'g', this method returns true if the given pattern contains
607 * "%g", and not just the substring "%g" (for example, in the case of
608 * "%%g").
609 *
610 * @param pattern The pattern to test.
611 * @param escape The escape character to search for.
612 * @return True iff the pattern contains the escape sequence with the
613 * given character.
614 */
615 private static boolean has (final String pattern, final char escape)
616 {
617 final int len = pattern.length ();
618 boolean sawPercent = false;
619 for (int i = 0; i < len; i++)
620 {
621 char c = pattern.charAt (i);
622 if (sawPercent)
623 {
624 if (c == escape)
625 return true;
626 if (c == '%') // Double percent
627 {
628 sawPercent = false;
629 continue;
630 }
631 }
632 sawPercent = (c == '%');
633 }
634 return false;
635 }
636
637 /**
638 * An output stream that tracks the number of bytes written to it.
639 */
640 private final class ostr extends FilterOutputStream
641 {
642 private ostr (OutputStream out)
643 {
644 super (out);
645 }
646
647 public void write (final int b) throws IOException
648 {
649 out.write (b);
650 FileHandler.this.written++; // FIXME: synchronize?
651 }
652
653 public void write (final byte[] b) throws IOException
654 {
655 write (b, 0, b.length);
656 }
657
658 public void write (final byte[] b, final int offset, final int length)
659 throws IOException
660 {
661 out.write (b, offset, length);
662 FileHandler.this.written += length; // FIXME: synchronize?
663 }
664 }
665 }