1 /* 2 * Copyright (c) 2000, 2006, 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 26 package java.util.logging; 27 28 import java.io.*; 29 import java.nio.channels.FileChannel; 30 import java.nio.channels.FileLock; 31 import 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 120 public 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 checkAccess(); 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 checkAccess(); 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 checkAccess(); 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 checkAccess(); 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 checkAccess(); 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.checkAccess(); 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 try { 413 FileLock fl = fc.tryLock(); 414 if (fl == null) { 415 // We failed to get the lock. Try next file. 416 continue; 417 } 418 // We got the lock OK. 419 } catch (IOException ix) { 420 // We got an IOException while trying to get the lock. 421 // This normally indicates that locking is not supported 422 // on the target directory. We have to proceed without 423 // getting a lock. Drop through. 424 } 425 // We got the lock. Remember it. 426 locks.put(lockFileName, lockFileName); 427 break; 428 } 429 } 430 431 files = new File[count]; 432 for (int i = 0; i < count; i++) { 433 files[i] = generate(pattern, i, unique); 434 } 435 436 // Create the initial log file. 437 if (append) { 438 open(files[0], true); 439 } else { 440 rotate(); 441 } 442 443 // Did we detect any exceptions during initialization? 444 Exception ex = em.lastException; 445 if (ex != null) { 446 if (ex instanceof IOException) { 447 throw (IOException) ex; 448 } else if (ex instanceof SecurityException) { 449 throw (SecurityException) ex; 450 } else { 451 throw new IOException("Exception: " + ex); 452 } 453 } 454 455 // Install the normal default ErrorManager. 456 setErrorManager(new ErrorManager()); 457 } 458 459 // Generate a filename from a pattern. 460 private File generate(String pattern, int generation, int unique) throws IOException { 461 File file = null; 462 String word = ""; 463 int ix = 0; 464 boolean sawg = false; 465 boolean sawu = false; 466 while (ix < pattern.length()) { 467 char ch = pattern.charAt(ix); 468 ix++; 469 char ch2 = 0; 470 if (ix < pattern.length()) { 471 ch2 = Character.toLowerCase(pattern.charAt(ix)); 472 } 473 if (ch == '/') { 474 if (file == null) { 475 file = new File(word); 476 } else { 477 file = new File(file, word); 478 } 479 word = ""; 480 continue; 481 } else if (ch == '%') { 482 if (ch2 == 't') { 483 String tmpDir = System.getProperty("java.io.tmpdir"); 484 if (tmpDir == null) { 485 tmpDir = System.getProperty("user.home"); 486 } 487 file = new File(tmpDir); 488 ix++; 489 word = ""; 490 continue; 491 } else if (ch2 == 'h') { 492 file = new File(System.getProperty("user.home")); 493 if (isSetUID()) { 494 // Ok, we are in a set UID program. For safety's sake 495 // we disallow attempts to open files relative to %h. 496 throw new IOException("can't use %h in set UID program"); 497 } 498 ix++; 499 word = ""; 500 continue; 501 } else if (ch2 == 'g') { 502 word = word + generation; 503 sawg = true; 504 ix++; 505 continue; 506 } else if (ch2 == 'u') { 507 word = word + unique; 508 sawu = true; 509 ix++; 510 continue; 511 } else if (ch2 == '%') { 512 word = word + "%"; 513 ix++; 514 continue; 515 } 516 } 517 word = word + ch; 518 } 519 if (count > 1 && !sawg) { 520 word = word + "." + generation; 521 } 522 if (unique > 0 && !sawu) { 523 word = word + "." + unique; 524 } 525 if (word.length() > 0) { 526 if (file == null) { 527 file = new File(word); 528 } else { 529 file = new File(file, word); 530 } 531 } 532 return file; 533 } 534 535 // Rotate the set of output files 536 private synchronized void rotate() { 537 Level oldLevel = getLevel(); 538 setLevel(Level.OFF); 539 540 super.close(); 541 for (int i = count-2; i >= 0; i--) { 542 File f1 = files[i]; 543 File f2 = files[i+1]; 544 if (f1.exists()) { 545 if (f2.exists()) { 546 f2.delete(); 547 } 548 f1.renameTo(f2); 549 } 550 } 551 try { 552 open(files[0], false); 553 } catch (IOException ix) { 554 // We don't want to throw an exception here, but we 555 // report the exception to any registered ErrorManager. 556 reportError(null, ix, ErrorManager.OPEN_FAILURE); 557 558 } 559 setLevel(oldLevel); 560 } 561 562 /** 563 * Format and publish a <tt>LogRecord</tt>. 564 * 565 * @param record description of the log event. A null record is 566 * silently ignored and is not published 567 */ 568 public synchronized void publish(LogRecord record) { 569 if (!isLoggable(record)) { 570 return; 571 } 572 super.publish(record); 573 flush(); 574 if (limit > 0 && meter.written >= limit) { 575 // We performed access checks in the "init" method to make sure 576 // we are only initialized from trusted code. So we assume 577 // it is OK to write the target files, even if we are 578 // currently being called from untrusted code. 579 // So it is safe to raise privilege here. 580 AccessController.doPrivileged(new PrivilegedAction<Object>() { 581 public Object run() { 582 rotate(); 583 return null; 584 } 585 }); 586 } 587 } 588 589 /** 590 * Close all the files. 591 * 592 * @exception SecurityException if a security manager exists and if 593 * the caller does not have <tt>LoggingPermission("control")</tt>. 594 */ 595 public synchronized void close() throws SecurityException { 596 super.close(); 597 // Unlock any lock file. 598 if (lockFileName == null) { 599 return; 600 } 601 try { 602 // Closing the lock file's FileOutputStream will close 603 // the underlying channel and free any locks. 604 lockStream.close(); 605 } catch (Exception ex) { 606 // Problems closing the stream. Punt. 607 } 608 synchronized(locks) { 609 locks.remove(lockFileName); 610 } 611 new File(lockFileName).delete(); 612 lockFileName = null; 613 lockStream = null; 614 } 615 616 private static class InitializationErrorManager extends ErrorManager { 617 Exception lastException; 618 public void error(String msg, Exception ex, int code) { 619 lastException = ex; 620 } 621 } 622 623 // Private native method to check if we are in a set UID program. 624 private static native boolean isSetUID(); 625 }