Source for java.util.logging.FileHandler

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