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