001 /* FileHandler.java -- a class for publishing log messages to log files 002 Copyright (C) 2002, 2003, 2004, 2005 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package java.util.logging; 040 041 import java.io.File; 042 import java.io.FileOutputStream; 043 import java.io.FilterOutputStream; 044 import java.io.IOException; 045 import java.io.OutputStream; 046 import java.util.LinkedList; 047 import java.util.ListIterator; 048 049 /** 050 * A <code>FileHandler</code> publishes log records to a set of log 051 * files. A maximum file size can be specified; as soon as a log file 052 * reaches the size limit, it is closed and the next file in the set 053 * is taken. 054 * 055 * <p><strong>Configuration:</strong> Values of the subsequent 056 * <code>LogManager</code> properties are taken into consideration 057 * when a <code>FileHandler</code> is initialized. If a property is 058 * not defined, or if it has an invalid value, a default is taken 059 * without an exception being thrown. 060 * 061 * <ul> 062 * 063 * <li><code>java.util.FileHandler.level</code> - specifies 064 * the initial severity level threshold. Default value: 065 * <code>Level.ALL</code>.</li> 066 * 067 * <li><code>java.util.FileHandler.filter</code> - specifies 068 * the name of a Filter class. Default value: No Filter.</li> 069 * 070 * <li><code>java.util.FileHandler.formatter</code> - specifies 071 * the name of a Formatter class. Default value: 072 * <code>java.util.logging.XMLFormatter</code>.</li> 073 * 074 * <li><code>java.util.FileHandler.encoding</code> - specifies 075 * the name of the character encoding. Default value: 076 * the default platform encoding.</li> 077 * 078 * <li><code>java.util.FileHandler.limit</code> - specifies the number 079 * of bytes a log file is approximately allowed to reach before it 080 * is closed and the handler switches to the next file in the 081 * rotating set. A value of zero means that files can grow 082 * without limit. Default value: 0 (unlimited growth).</li> 083 * 084 * <li><code>java.util.FileHandler.count</code> - specifies the number 085 * of log files through which this handler cycles. Default value: 086 * 1.</li> 087 * 088 * <li><code>java.util.FileHandler.pattern</code> - specifies a 089 * pattern for the location and name of the produced log files. 090 * See the section on <a href="#filePatterns">file name 091 * patterns</a> for details. Default value: 092 * <code>"%h/java%u.log"</code>.</li> 093 * 094 * <li><code>java.util.FileHandler.append</code> - specifies 095 * whether the handler will append log records to existing 096 * files, or whether the handler will clear log files 097 * upon switching to them. Default value: <code>false</code>, 098 * indicating that files will be cleared.</li> 099 * 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 }