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