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