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    }