OSDN Git Service

2007-02-09 Jakub Jelinek <jakub@redhat.com>
[pf3gnuchains/gcc-fork.git] / libjava / classpath / java / util / logging / FileHandler.java
1 /* FileHandler.java -- a class for publishing log messages to log files
2    Copyright (C) 2002, 2003, 2004, 2005  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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.
10
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.
15
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.
20
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.
25
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. */
37
38
39 package java.util.logging;
40
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;
48
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.
54  *
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.
60  *
61  * <ul>
62  *
63  * <li><code>java.util.FileHandler.level</code> - specifies
64  *     the initial severity level threshold. Default value:
65  *     <code>Level.ALL</code>.</li>
66  *
67  * <li><code>java.util.FileHandler.filter</code> - specifies
68  *     the name of a Filter class. Default value: No Filter.</li>
69  *
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>
73  *
74  * <li><code>java.util.FileHandler.encoding</code> - specifies
75  *     the name of the character encoding. Default value:
76  *     the default platform encoding.</li>
77  *
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>
83  *
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>
87  *
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>
93  *
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>
99  *
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 }