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    }