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