GNU Classpath (0.20) | |
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: 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: }
GNU Classpath (0.20) |