FileHandler.java revision 51b1b6997fd3f980076b8081f7f1165ccc2a4008
1/* 2 * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package java.util.logging; 27 28import java.io.*; 29import java.nio.channels.FileChannel; 30import java.nio.channels.FileLock; 31import java.security.*; 32 33/** 34 * Simple file logging <tt>Handler</tt>. 35 * <p> 36 * The <tt>FileHandler</tt> can either write to a specified file, 37 * or it can write to a rotating set of files. 38 * <p> 39 * For a rotating set of files, as each file reaches a given size 40 * limit, it is closed, rotated out, and a new file opened. 41 * Successively older files are named by adding "0", "1", "2", 42 * etc. into the base filename. 43 * <p> 44 * By default buffering is enabled in the IO libraries but each log 45 * record is flushed out when it is complete. 46 * <p> 47 * By default the <tt>XMLFormatter</tt> class is used for formatting. 48 * <p> 49 * <b>Configuration:</b> 50 * By default each <tt>FileHandler</tt> is initialized using the following 51 * <tt>LogManager</tt> configuration properties. If properties are not defined 52 * (or have invalid values) then the specified default values are used. 53 * <ul> 54 * <li> java.util.logging.FileHandler.level 55 * specifies the default level for the <tt>Handler</tt> 56 * (defaults to <tt>Level.ALL</tt>). 57 * <li> java.util.logging.FileHandler.filter 58 * specifies the name of a <tt>Filter</tt> class to use 59 * (defaults to no <tt>Filter</tt>). 60 * <li> java.util.logging.FileHandler.formatter 61 * specifies the name of a <tt>Formatter</tt> class to use 62 * (defaults to <tt>java.util.logging.XMLFormatter</tt>) 63 * <li> java.util.logging.FileHandler.encoding 64 * the name of the character set encoding to use (defaults to 65 * the default platform encoding). 66 * <li> java.util.logging.FileHandler.limit 67 * specifies an approximate maximum amount to write (in bytes) 68 * to any one file. If this is zero, then there is no limit. 69 * (Defaults to no limit). 70 * <li> java.util.logging.FileHandler.count 71 * specifies how many output files to cycle through (defaults to 1). 72 * <li> java.util.logging.FileHandler.pattern 73 * specifies a pattern for generating the output file name. See 74 * below for details. (Defaults to "%h/java%u.log"). 75 * <li> java.util.logging.FileHandler.append 76 * specifies whether the FileHandler should append onto 77 * any existing files (defaults to false). 78 * </ul> 79 * <p> 80 * <p> 81 * A pattern consists of a string that includes the following special 82 * components that will be replaced at runtime: 83 * <ul> 84 * <li> "/" the local pathname separator 85 * <li> "%t" the system temporary directory 86 * <li> "%h" the value of the "user.home" system property 87 * <li> "%g" the generation number to distinguish rotated logs 88 * <li> "%u" a unique number to resolve conflicts 89 * <li> "%%" translates to a single percent sign "%" 90 * </ul> 91 * If no "%g" field has been specified and the file count is greater 92 * than one, then the generation number will be added to the end of 93 * the generated filename, after a dot. 94 * <p> 95 * Thus for example a pattern of "%t/java%g.log" with a count of 2 96 * would typically cause log files to be written on Solaris to 97 * /var/tmp/java0.log and /var/tmp/java1.log whereas on Windows 95 they 98 * would be typically written to C:\TEMP\java0.log and C:\TEMP\java1.log 99 * <p> 100 * Generation numbers follow the sequence 0, 1, 2, etc. 101 * <p> 102 * Normally the "%u" unique field is set to 0. However, if the <tt>FileHandler</tt> 103 * tries to open the filename and finds the file is currently in use by 104 * another process it will increment the unique number field and try 105 * again. This will be repeated until <tt>FileHandler</tt> finds a file name that 106 * is not currently in use. If there is a conflict and no "%u" field has 107 * been specified, it will be added at the end of the filename after a dot. 108 * (This will be after any automatically added generation number.) 109 * <p> 110 * Thus if three processes were all trying to log to fred%u.%g.txt then 111 * they might end up using fred0.0.txt, fred1.0.txt, fred2.0.txt as 112 * the first file in their rotating sequences. 113 * <p> 114 * Note that the use of unique ids to avoid conflicts is only guaranteed 115 * to work reliably when using a local disk file system. 116 * 117 * @since 1.4 118 */ 119 120public class FileHandler extends StreamHandler { 121 private MeteredStream meter; 122 private boolean append; 123 private int limit; // zero => no limit. 124 private int count; 125 private String pattern; 126 private String lockFileName; 127 private FileOutputStream lockStream; 128 private File files[]; 129 private static final int MAX_LOCKS = 100; 130 private static java.util.HashMap<String, String> locks = new java.util.HashMap<>(); 131 132 // A metered stream is a subclass of OutputStream that 133 // (a) forwards all its output to a target stream 134 // (b) keeps track of how many bytes have been written 135 private class MeteredStream extends OutputStream { 136 OutputStream out; 137 int written; 138 139 MeteredStream(OutputStream out, int written) { 140 this.out = out; 141 this.written = written; 142 } 143 144 public void write(int b) throws IOException { 145 out.write(b); 146 written++; 147 } 148 149 public void write(byte buff[]) throws IOException { 150 out.write(buff); 151 written += buff.length; 152 } 153 154 public void write(byte buff[], int off, int len) throws IOException { 155 out.write(buff,off,len); 156 written += len; 157 } 158 159 public void flush() throws IOException { 160 out.flush(); 161 } 162 163 public void close() throws IOException { 164 out.close(); 165 } 166 } 167 168 private void open(File fname, boolean append) throws IOException { 169 int len = 0; 170 if (append) { 171 len = (int)fname.length(); 172 } 173 FileOutputStream fout = new FileOutputStream(fname.toString(), append); 174 BufferedOutputStream bout = new BufferedOutputStream(fout); 175 meter = new MeteredStream(bout, len); 176 setOutputStream(meter); 177 } 178 179 // Private method to configure a FileHandler from LogManager 180 // properties and/or default values as specified in the class 181 // javadoc. 182 private void configure() { 183 LogManager manager = LogManager.getLogManager(); 184 185 String cname = getClass().getName(); 186 187 pattern = manager.getStringProperty(cname + ".pattern", "%h/java%u.log"); 188 limit = manager.getIntProperty(cname + ".limit", 0); 189 if (limit < 0) { 190 limit = 0; 191 } 192 count = manager.getIntProperty(cname + ".count", 1); 193 if (count <= 0) { 194 count = 1; 195 } 196 append = manager.getBooleanProperty(cname + ".append", false); 197 setLevel(manager.getLevelProperty(cname + ".level", Level.ALL)); 198 setFilter(manager.getFilterProperty(cname + ".filter", null)); 199 setFormatter(manager.getFormatterProperty(cname + ".formatter", new XMLFormatter())); 200 try { 201 setEncoding(manager.getStringProperty(cname +".encoding", null)); 202 } catch (Exception ex) { 203 try { 204 setEncoding(null); 205 } catch (Exception ex2) { 206 // doing a setEncoding with null should always work. 207 // assert false; 208 } 209 } 210 } 211 212 213 /** 214 * Construct a default <tt>FileHandler</tt>. This will be configured 215 * entirely from <tt>LogManager</tt> properties (or their default values). 216 * <p> 217 * @exception IOException if there are IO problems opening the files. 218 * @exception SecurityException if a security manager exists and if 219 * the caller does not have <tt>LoggingPermission("control"))</tt>. 220 * @exception NullPointerException if pattern property is an empty String. 221 */ 222 public FileHandler() throws IOException, SecurityException { 223 checkPermission(); 224 configure(); 225 openFiles(); 226 } 227 228 /** 229 * Initialize a <tt>FileHandler</tt> to write to the given filename. 230 * <p> 231 * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt> 232 * properties (or their default values) except that the given pattern 233 * argument is used as the filename pattern, the file limit is 234 * set to no limit, and the file count is set to one. 235 * <p> 236 * There is no limit on the amount of data that may be written, 237 * so use this with care. 238 * 239 * @param pattern the name of the output file 240 * @exception IOException if there are IO problems opening the files. 241 * @exception SecurityException if a security manager exists and if 242 * the caller does not have <tt>LoggingPermission("control")</tt>. 243 * @exception IllegalArgumentException if pattern is an empty string 244 */ 245 public FileHandler(String pattern) throws IOException, SecurityException { 246 if (pattern.length() < 1 ) { 247 throw new IllegalArgumentException(); 248 } 249 checkPermission(); 250 configure(); 251 this.pattern = pattern; 252 this.limit = 0; 253 this.count = 1; 254 openFiles(); 255 } 256 257 /** 258 * Initialize a <tt>FileHandler</tt> to write to the given filename, 259 * with optional append. 260 * <p> 261 * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt> 262 * properties (or their default values) except that the given pattern 263 * argument is used as the filename pattern, the file limit is 264 * set to no limit, the file count is set to one, and the append 265 * mode is set to the given <tt>append</tt> argument. 266 * <p> 267 * There is no limit on the amount of data that may be written, 268 * so use this with care. 269 * 270 * @param pattern the name of the output file 271 * @param append specifies append mode 272 * @exception IOException if there are IO problems opening the files. 273 * @exception SecurityException if a security manager exists and if 274 * the caller does not have <tt>LoggingPermission("control")</tt>. 275 * @exception IllegalArgumentException if pattern is an empty string 276 */ 277 public FileHandler(String pattern, boolean append) throws IOException, SecurityException { 278 if (pattern.length() < 1 ) { 279 throw new IllegalArgumentException(); 280 } 281 checkPermission(); 282 configure(); 283 this.pattern = pattern; 284 this.limit = 0; 285 this.count = 1; 286 this.append = append; 287 openFiles(); 288 } 289 290 /** 291 * Initialize a <tt>FileHandler</tt> to write to a set of files. When 292 * (approximately) the given limit has been written to one file, 293 * another file will be opened. The output will cycle through a set 294 * of count files. 295 * <p> 296 * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt> 297 * properties (or their default values) except that the given pattern 298 * argument is used as the filename pattern, the file limit is 299 * set to the limit argument, and the file count is set to the 300 * given count argument. 301 * <p> 302 * The count must be at least 1. 303 * 304 * @param pattern the pattern for naming the output file 305 * @param limit the maximum number of bytes to write to any one file 306 * @param count the number of files to use 307 * @exception IOException if there are IO problems opening the files. 308 * @exception SecurityException if a security manager exists and if 309 * the caller does not have <tt>LoggingPermission("control")</tt>. 310 * @exception IllegalArgumentException if limit < 0, or count < 1. 311 * @exception IllegalArgumentException if pattern is an empty string 312 */ 313 public FileHandler(String pattern, int limit, int count) 314 throws IOException, SecurityException { 315 if (limit < 0 || count < 1 || pattern.length() < 1) { 316 throw new IllegalArgumentException(); 317 } 318 checkPermission(); 319 configure(); 320 this.pattern = pattern; 321 this.limit = limit; 322 this.count = count; 323 openFiles(); 324 } 325 326 /** 327 * Initialize a <tt>FileHandler</tt> to write to a set of files 328 * with optional append. When (approximately) the given limit has 329 * been written to one file, another file will be opened. The 330 * output will cycle through a set of count files. 331 * <p> 332 * The <tt>FileHandler</tt> is configured based on <tt>LogManager</tt> 333 * properties (or their default values) except that the given pattern 334 * argument is used as the filename pattern, the file limit is 335 * set to the limit argument, and the file count is set to the 336 * given count argument, and the append mode is set to the given 337 * <tt>append</tt> argument. 338 * <p> 339 * The count must be at least 1. 340 * 341 * @param pattern the pattern for naming the output file 342 * @param limit the maximum number of bytes to write to any one file 343 * @param count the number of files to use 344 * @param append specifies append mode 345 * @exception IOException if there are IO problems opening the files. 346 * @exception SecurityException if a security manager exists and if 347 * the caller does not have <tt>LoggingPermission("control")</tt>. 348 * @exception IllegalArgumentException if limit < 0, or count < 1. 349 * @exception IllegalArgumentException if pattern is an empty string 350 * 351 */ 352 public FileHandler(String pattern, int limit, int count, boolean append) 353 throws IOException, SecurityException { 354 if (limit < 0 || count < 1 || pattern.length() < 1) { 355 throw new IllegalArgumentException(); 356 } 357 checkPermission(); 358 configure(); 359 this.pattern = pattern; 360 this.limit = limit; 361 this.count = count; 362 this.append = append; 363 openFiles(); 364 } 365 366 // Private method to open the set of output files, based on the 367 // configured instance variables. 368 private void openFiles() throws IOException { 369 LogManager manager = LogManager.getLogManager(); 370 manager.checkPermission(); 371 if (count < 1) { 372 throw new IllegalArgumentException("file count = " + count); 373 } 374 if (limit < 0) { 375 limit = 0; 376 } 377 378 // We register our own ErrorManager during initialization 379 // so we can record exceptions. 380 InitializationErrorManager em = new InitializationErrorManager(); 381 setErrorManager(em); 382 383 // Create a lock file. This grants us exclusive access 384 // to our set of output files, as long as we are alive. 385 int unique = -1; 386 for (;;) { 387 unique++; 388 if (unique > MAX_LOCKS) { 389 throw new IOException("Couldn't get lock for " + pattern); 390 } 391 // Generate a lock file name from the "unique" int. 392 lockFileName = generate(pattern, 0, unique).toString() + ".lck"; 393 // Now try to lock that filename. 394 // Because some systems (e.g., Solaris) can only do file locks 395 // between processes (and not within a process), we first check 396 // if we ourself already have the file locked. 397 synchronized(locks) { 398 if (locks.get(lockFileName) != null) { 399 // We already own this lock, for a different FileHandler 400 // object. Try again. 401 continue; 402 } 403 FileChannel fc; 404 try { 405 lockStream = new FileOutputStream(lockFileName); 406 fc = lockStream.getChannel(); 407 } catch (IOException ix) { 408 // We got an IOException while trying to open the file. 409 // Try the next file. 410 continue; 411 } 412 boolean available; 413 try { 414 available = fc.tryLock() != null; 415 // We got the lock OK. 416 } catch (IOException ix) { 417 // We got an IOException while trying to get the lock. 418 // This normally indicates that locking is not supported 419 // on the target directory. We have to proceed without 420 // getting a lock. Drop through. 421 available = true; 422 } 423 if (available) { 424 // We got the lock. Remember it. 425 locks.put(lockFileName, lockFileName); 426 break; 427 } 428 429 // We failed to get the lock. Try next file. 430 fc.close(); 431 } 432 } 433 434 files = new File[count]; 435 for (int i = 0; i < count; i++) { 436 files[i] = generate(pattern, i, unique); 437 } 438 439 // Create the initial log file. 440 if (append) { 441 open(files[0], true); 442 } else { 443 rotate(); 444 } 445 446 // Did we detect any exceptions during initialization? 447 Exception ex = em.lastException; 448 if (ex != null) { 449 if (ex instanceof IOException) { 450 throw (IOException) ex; 451 } else if (ex instanceof SecurityException) { 452 throw (SecurityException) ex; 453 } else { 454 throw new IOException("Exception: " + ex); 455 } 456 } 457 458 // Install the normal default ErrorManager. 459 setErrorManager(new ErrorManager()); 460 } 461 462 // Generate a filename from a pattern. 463 private File generate(String pattern, int generation, int unique) throws IOException { 464 File file = null; 465 String word = ""; 466 int ix = 0; 467 boolean sawg = false; 468 boolean sawu = false; 469 while (ix < pattern.length()) { 470 char ch = pattern.charAt(ix); 471 ix++; 472 char ch2 = 0; 473 if (ix < pattern.length()) { 474 ch2 = Character.toLowerCase(pattern.charAt(ix)); 475 } 476 if (ch == '/') { 477 if (file == null) { 478 file = new File(word); 479 } else { 480 file = new File(file, word); 481 } 482 word = ""; 483 continue; 484 } else if (ch == '%') { 485 if (ch2 == 't') { 486 String tmpDir = System.getProperty("java.io.tmpdir"); 487 if (tmpDir == null) { 488 tmpDir = System.getProperty("user.home"); 489 } 490 file = new File(tmpDir); 491 ix++; 492 word = ""; 493 continue; 494 } else if (ch2 == 'h') { 495 file = new File(System.getProperty("user.home")); 496 if (isSetUID()) { 497 // Ok, we are in a set UID program. For safety's sake 498 // we disallow attempts to open files relative to %h. 499 throw new IOException("can't use %h in set UID program"); 500 } 501 ix++; 502 word = ""; 503 continue; 504 } else if (ch2 == 'g') { 505 word = word + generation; 506 sawg = true; 507 ix++; 508 continue; 509 } else if (ch2 == 'u') { 510 word = word + unique; 511 sawu = true; 512 ix++; 513 continue; 514 } else if (ch2 == '%') { 515 word = word + "%"; 516 ix++; 517 continue; 518 } 519 } 520 word = word + ch; 521 } 522 if (count > 1 && !sawg) { 523 word = word + "." + generation; 524 } 525 if (unique > 0 && !sawu) { 526 word = word + "." + unique; 527 } 528 if (word.length() > 0) { 529 if (file == null) { 530 file = new File(word); 531 } else { 532 file = new File(file, word); 533 } 534 } 535 return file; 536 } 537 538 // Rotate the set of output files 539 private synchronized void rotate() { 540 Level oldLevel = getLevel(); 541 setLevel(Level.OFF); 542 543 super.close(); 544 for (int i = count-2; i >= 0; i--) { 545 File f1 = files[i]; 546 File f2 = files[i+1]; 547 if (f1.exists()) { 548 if (f2.exists()) { 549 f2.delete(); 550 } 551 f1.renameTo(f2); 552 } 553 } 554 try { 555 open(files[0], false); 556 } catch (IOException ix) { 557 // We don't want to throw an exception here, but we 558 // report the exception to any registered ErrorManager. 559 reportError(null, ix, ErrorManager.OPEN_FAILURE); 560 561 } 562 setLevel(oldLevel); 563 } 564 565 /** 566 * Format and publish a <tt>LogRecord</tt>. 567 * 568 * @param record description of the log event. A null record is 569 * silently ignored and is not published 570 */ 571 public synchronized void publish(LogRecord record) { 572 if (!isLoggable(record)) { 573 return; 574 } 575 super.publish(record); 576 flush(); 577 if (limit > 0 && meter.written >= limit) { 578 // We performed access checks in the "init" method to make sure 579 // we are only initialized from trusted code. So we assume 580 // it is OK to write the target files, even if we are 581 // currently being called from untrusted code. 582 // So it is safe to raise privilege here. 583 AccessController.doPrivileged(new PrivilegedAction<Object>() { 584 public Object run() { 585 rotate(); 586 return null; 587 } 588 }); 589 } 590 } 591 592 /** 593 * Close all the files. 594 * 595 * @exception SecurityException if a security manager exists and if 596 * the caller does not have <tt>LoggingPermission("control")</tt>. 597 */ 598 public synchronized void close() throws SecurityException { 599 super.close(); 600 // Unlock any lock file. 601 if (lockFileName == null) { 602 return; 603 } 604 try { 605 // Closing the lock file's FileOutputStream will close 606 // the underlying channel and free any locks. 607 lockStream.close(); 608 } catch (Exception ex) { 609 // Problems closing the stream. Punt. 610 } 611 synchronized(locks) { 612 locks.remove(lockFileName); 613 } 614 new File(lockFileName).delete(); 615 lockFileName = null; 616 lockStream = null; 617 } 618 619 private static class InitializationErrorManager extends ErrorManager { 620 Exception lastException; 621 public void error(String msg, Exception ex, int code) { 622 lastException = ex; 623 } 624 } 625 626 // Private native method to check if we are in a set UID program. 627 private static native boolean isSetUID(); 628} 629