GNU Classpath (0.17) | ||
Frames | No Frames |
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: }
GNU Classpath (0.17) |