001/* FileHandler.java -- a class for publishing log messages to log files
002   Copyright (C) 2002, 2003, 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.util.logging;
040
041import gnu.java.lang.CPStringBuilder;
042
043import java.io.File;
044import java.io.FileOutputStream;
045import java.io.FilterOutputStream;
046import java.io.IOException;
047import java.io.OutputStream;
048import java.util.LinkedList;
049import 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 */
193public 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}