1 /* 2 * Copyright (c) 2000, 2013, 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 static java.nio.file.StandardOpenOption.APPEND; 29 import static java.nio.file.StandardOpenOption.CREATE_NEW; 30 import static java.nio.file.StandardOpenOption.WRITE; 31 32 import java.io.BufferedOutputStream; 33 import java.io.File; 34 import java.io.FileOutputStream; 35 import java.io.IOException; 36 import java.io.OutputStream; 37 import java.nio.channels.FileChannel; 38 import java.nio.channels.OverlappingFileLockException; 39 import java.nio.file.FileAlreadyExistsException; 40 import java.nio.file.Files; 41 import java.nio.file.LinkOption; 42 import java.nio.file.NoSuchFileException; 43 import java.nio.file.Path; 44 import java.nio.file.Paths; 45 import java.security.AccessController; 46 import java.security.PrivilegedAction; 47 import java.util.HashSet; 48 import java.util.Set; 49 50 /** 51 * Simple file logging {@code Handler}. 52 * <p> 53 * The {@code FileHandler} can either write to a specified file, 54 * or it can write to a rotating set of files. 55 * <p> 56 * For a rotating set of files, as each file reaches a given size 57 * limit, it is closed, rotated out, and a new file opened. 58 * Successively older files are named by adding "0", "1", "2", 59 * etc. into the base filename. 60 * <p> 61 * By default buffering is enabled in the IO libraries but each log 62 * record is flushed out when it is complete. 63 * <p> 64 * By default the {@code XMLFormatter} class is used for formatting. 65 * <p> 66 * <b>Configuration:</b> 67 * By default each {@code FileHandler} is initialized using the following 68 * {@code LogManager} configuration properties where {@code <handler-name>} 69 * refers to the fully-qualified class name of the handler. 70 * If properties are not defined 71 * (or have invalid values) then the specified default values are used. 72 * <ul> 73 * <li> <handler-name>.level 74 * specifies the default level for the {@code Handler} 75 * (defaults to {@code Level.ALL}). </li> 76 * <li> <handler-name>.filter 77 * specifies the name of a {@code Filter} class to use 78 * (defaults to no {@code Filter}). </li> 79 * <li> <handler-name>.formatter 80 * specifies the name of a {@code Formatter} class to use 81 * (defaults to {@code java.util.logging.XMLFormatter}) </li> 82 * <li> <handler-name>.encoding 83 * the name of the character set encoding to use (defaults to 84 * the default platform encoding). </li> 85 * <li> <handler-name>.limit 86 * specifies an approximate maximum amount to write (in bytes) 87 * to any one file. If this is zero, then there is no limit. 88 * (Defaults to no limit). </li> 89 * <li> <handler-name>.count 90 * specifies how many output files to cycle through (defaults to 1). </li> 91 * <li> <handler-name>.pattern 92 * specifies a pattern for generating the output file name. See 93 * below for details. (Defaults to "%h/java%u.log"). </li> 94 * <li> <handler-name>.append 95 * specifies whether the FileHandler should append onto 96 * any existing files (defaults to false). </li> 97 * </ul> 98 * <p> 99 * For example, the properties for {@code FileHandler} would be: 100 * <ul> 101 * <li> java.util.logging.FileHandler.level=INFO </li> 102 * <li> java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter </li> 103 * </ul> 104 * <p> 105 * For a custom handler, e.g. com.foo.MyHandler, the properties would be: 106 * <ul> 107 * <li> com.foo.MyHandler.level=INFO </li> 108 * <li> com.foo.MyHandler.formatter=java.util.logging.SimpleFormatter </li> 109 * </ul> 110 * <p> 111 * A pattern consists of a string that includes the following special 112 * components that will be replaced at runtime: 113 * <ul> 114 * <li> "/" the local pathname separator </li> 115 * <li> "%t" the system temporary directory </li> 116 * <li> "%h" the value of the "user.home" system property </li> 117 * <li> "%g" the generation number to distinguish rotated logs </li> 118 * <li> "%u" a unique number to resolve conflicts </li> 119 * <li> "%%" translates to a single percent sign "%" </li> 120 * </ul> 121 * If no "%g" field has been specified and the file count is greater 122 * than one, then the generation number will be added to the end of 123 * the generated filename, after a dot. 124 * <p> 125 * Thus for example a pattern of "%t/java%g.log" with a count of 2 126 * would typically cause log files to be written on Solaris to 127 * /var/tmp/java0.log and /var/tmp/java1.log whereas on Windows 95 they 128 * would be typically written to C:\TEMP\java0.log and C:\TEMP\java1.log 129 * <p> 130 * Generation numbers follow the sequence 0, 1, 2, etc. 131 * <p> 132 * Normally the "%u" unique field is set to 0. However, if the {@code FileHandler} 133 * tries to open the filename and finds the file is currently in use by 134 * another process it will increment the unique number field and try 135 * again. This will be repeated until {@code FileHandler} finds a file name that 136 * is not currently in use. If there is a conflict and no "%u" field has 137 * been specified, it will be added at the end of the filename after a dot. 138 * (This will be after any automatically added generation number.) 139 * <p> 140 * Thus if three processes were all trying to log to fred%u.%g.txt then 141 * they might end up using fred0.0.txt, fred1.0.txt, fred2.0.txt as 142 * the first file in their rotating sequences. 143 * <p> 144 * Note that the use of unique ids to avoid conflicts is only guaranteed 145 * to work reliably when using a local disk file system. 146 * 147 * @since 1.4 148 */ 149 150 public class FileHandler extends StreamHandler { 151 private MeteredStream meter; 152 private boolean append; 153 private long limit; // zero => no limit. 154 private int count; 155 private String pattern; 156 private String lockFileName; 157 private FileChannel lockFileChannel; 158 private File files[]; 159 private static final int MAX_LOCKS = 100; 160 private static final Set<String> locks = new HashSet<>(); 161 162 /** 163 * A metered stream is a subclass of OutputStream that 164 * (a) forwards all its output to a target stream 165 * (b) keeps track of how many bytes have been written 166 */ 167 private static final class MeteredStream extends OutputStream { 168 final OutputStream out; 169 long written; 170 171 MeteredStream(OutputStream out, long written) { 172 this.out = out; 173 this.written = written; 174 } 175 176 @Override 177 public void write(int b) throws IOException { 178 out.write(b); 179 written++; 180 } 181 182 @Override 183 public void write(byte buff[]) throws IOException { 184 out.write(buff); 185 written += buff.length; 186 } 187 188 @Override 189 public void write(byte buff[], int off, int len) throws IOException { 190 out.write(buff,off,len); 191 written += len; 192 } 193 194 @Override 195 public void flush() throws IOException { 196 out.flush(); 197 } 198 199 @Override 200 public void close() throws IOException { 201 out.close(); 202 } 203 } 204 205 private void open(File fname, boolean append) throws IOException { 206 long len = 0; 207 if (append) { 208 len = fname.length(); 209 } 210 FileOutputStream fout = new FileOutputStream(fname.toString(), append); 211 BufferedOutputStream bout = new BufferedOutputStream(fout); 212 meter = new MeteredStream(bout, len); 213 setOutputStream(meter); 214 } 215 216 /** 217 * Configure a FileHandler from LogManager properties and/or default values 218 * as specified in the class javadoc. 219 */ 220 private void configure() { 221 LogManager manager = LogManager.getLogManager(); 222 223 String cname = getClass().getName(); 224 225 pattern = manager.getStringProperty(cname + ".pattern", "%h/java%u.log"); 226 limit = manager.getLongProperty(cname + ".limit", 0); 227 if (limit < 0) { 228 limit = 0; 229 } 230 count = manager.getIntProperty(cname + ".count", 1); 231 if (count <= 0) { 232 count = 1; 233 } 234 append = manager.getBooleanProperty(cname + ".append", false); 235 setLevel(manager.getLevelProperty(cname + ".level", Level.ALL)); 236 setFilter(manager.getFilterProperty(cname + ".filter", null)); 237 setFormatter(manager.getFormatterProperty(cname + ".formatter", new XMLFormatter())); 238 try { 239 setEncoding(manager.getStringProperty(cname +".encoding", null)); 240 } catch (Exception ex) { 241 try { 242 setEncoding(null); 243 } catch (Exception ex2) { 244 // doing a setEncoding with null should always work. 245 // assert false; 246 } 247 } 248 } 249 250 251 /** 252 * Construct a default {@code FileHandler}. This will be configured 253 * entirely from {@code LogManager} properties (or their default values). 254 * 255 * @exception IOException if there are IO problems opening the files. 256 * @exception SecurityException if a security manager exists and if 257 * the caller does not have {@code LoggingPermission("control"))}. 258 * @exception NullPointerException if pattern property is an empty String. 259 */ 260 public FileHandler() throws IOException, SecurityException { 261 checkPermission(); 262 configure(); 263 // pattern will have been set by configure. check that it's not 264 // empty. 265 if (pattern.isEmpty()) { 266 throw new NullPointerException(); 267 } 268 openFiles(); 269 } 270 271 /** 272 * Initialize a {@code FileHandler} to write to the given filename. 273 * <p> 274 * The {@code FileHandler} is configured based on {@code LogManager} 275 * properties (or their default values) except that the given pattern 276 * argument is used as the filename pattern, the file limit is 277 * set to no limit, and the file count is set to one. 278 * <p> 279 * There is no limit on the amount of data that may be written, 280 * so use this with care. 281 * 282 * @param pattern the name of the output file 283 * @exception IOException if there are IO problems opening the files. 284 * @exception SecurityException if a security manager exists and if 285 * the caller does not have {@code LoggingPermission("control")}. 286 * @exception IllegalArgumentException if pattern is an empty string 287 */ 288 public FileHandler(String pattern) throws IOException, SecurityException { 289 if (pattern.length() < 1 ) { 290 throw new IllegalArgumentException(); 291 } 292 checkPermission(); 293 configure(); 294 this.pattern = pattern; 295 this.limit = 0; 296 this.count = 1; 297 openFiles(); 298 } 299 300 /** 301 * Initialize a {@code FileHandler} to write to the given filename, 302 * with optional append. 303 * <p> 304 * The {@code FileHandler} is configured based on {@code LogManager} 305 * properties (or their default values) except that the given pattern 306 * argument is used as the filename pattern, the file limit is 307 * set to no limit, the file count is set to one, and the append 308 * mode is set to the given {@code append} argument. 309 * <p> 310 * There is no limit on the amount of data that may be written, 311 * so use this with care. 312 * 313 * @param pattern the name of the output file 314 * @param append specifies append mode 315 * @exception IOException if there are IO problems opening the files. 316 * @exception SecurityException if a security manager exists and if 317 * the caller does not have {@code LoggingPermission("control")}. 318 * @exception IllegalArgumentException if pattern is an empty string 319 */ 320 public FileHandler(String pattern, boolean append) throws IOException, 321 SecurityException { 322 if (pattern.length() < 1 ) { 323 throw new IllegalArgumentException(); 324 } 325 checkPermission(); 326 configure(); 327 this.pattern = pattern; 328 this.limit = 0; 329 this.count = 1; 330 this.append = append; 331 openFiles(); 332 } 333 334 /** 335 * Initialize a {@code FileHandler} to write to a set of files. When 336 * (approximately) the given limit has been written to one file, 337 * another file will be opened. The output will cycle through a set 338 * of count files. 339 * <p> 340 * The {@code FileHandler} is configured based on {@code LogManager} 341 * properties (or their default values) except that the given pattern 342 * argument is used as the filename pattern, the file limit is 343 * set to the limit argument, and the file count is set to the 344 * given count argument. 345 * <p> 346 * The count must be at least 1. 347 * 348 * @param pattern the pattern for naming the output file 349 * @param limit the maximum number of bytes to write to any one file 350 * @param count the number of files to use 351 * @exception IOException if there are IO problems opening the files. 352 * @exception SecurityException if a security manager exists and if 353 * the caller does not have {@code LoggingPermission("control")}. 354 * @exception IllegalArgumentException if {@code limit < 0}, or {@code count < 1}. 355 * @exception IllegalArgumentException if pattern is an empty string 356 */ 357 public FileHandler(String pattern, int limit, int count) 358 throws IOException, SecurityException { 359 if (limit < 0 || count < 1 || pattern.length() < 1) { 360 throw new IllegalArgumentException(); 361 } 362 checkPermission(); 363 configure(); 364 this.pattern = pattern; 365 this.limit = limit; 366 this.count = count; 367 openFiles(); 368 } 369 370 /** 371 * Initialize a {@code FileHandler} to write to a set of files 372 * with optional append. When (approximately) the given limit has 373 * been written to one file, another file will be opened. The 374 * output will cycle through a set of count files. 375 * <p> 376 * The {@code FileHandler} is configured based on {@code LogManager} 377 * properties (or their default values) except that the given pattern 378 * argument is used as the filename pattern, the file limit is 379 * set to the limit argument, and the file count is set to the 380 * given count argument, and the append mode is set to the given 381 * {@code append} argument. 382 * <p> 383 * The count must be at least 1. 384 * 385 * @param pattern the pattern for naming the output file 386 * @param limit the maximum number of bytes to write to any one file 387 * @param count the number of files to use 388 * @param append specifies append mode 389 * @exception IOException if there are IO problems opening the files. 390 * @exception SecurityException if a security manager exists and if 391 * the caller does not have {@code LoggingPermission("control")}. 392 * @exception IllegalArgumentException if {@code limit < 0}, or {@code count < 1}. 393 * @exception IllegalArgumentException if pattern is an empty string 394 * 395 */ 396 public FileHandler(String pattern, int limit, int count, boolean append) 397 throws IOException, SecurityException { 398 this(pattern, (long)limit, count, append); 399 } 400 401 /** 402 * Initialize a {@code FileHandler} to write to a set of files 403 * with optional append. When (approximately) the given limit has 404 * been written to one file, another file will be opened. The 405 * output will cycle through a set of count files. 406 * <p> 407 * The {@code FileHandler} is configured based on {@code LogManager} 408 * properties (or their default values) except that the given pattern 409 * argument is used as the filename pattern, the file limit is 410 * set to the limit argument, and the file count is set to the 411 * given count argument, and the append mode is set to the given 412 * {@code append} argument. 413 * <p> 414 * The count must be at least 1. 415 * 416 * @param pattern the pattern for naming the output file 417 * @param limit the maximum number of bytes to write to any one file 418 * @param count the number of files to use 419 * @param append specifies append mode 420 * @exception IOException if there are IO problems opening the files. 421 * @exception SecurityException if a security manager exists and if 422 * the caller does not have {@code LoggingPermission("control")}. 423 * @exception IllegalArgumentException if {@code limit < 0}, or {@code count < 1}. 424 * @exception IllegalArgumentException if pattern is an empty string 425 * 426 * @since 9 427 * 428 */ 429 public FileHandler(String pattern, long limit, int count, boolean append) 430 throws IOException { 431 if (limit < 0 || count < 1 || pattern.length() < 1) { 432 throw new IllegalArgumentException(); 433 } 434 checkPermission(); 435 configure(); 436 this.pattern = pattern; 437 this.limit = limit; 438 this.count = count; 439 this.append = append; 440 openFiles(); 441 } 442 443 private boolean isParentWritable(Path path) { 444 Path parent = path.getParent(); 445 if (parent == null) { 446 parent = path.toAbsolutePath().getParent(); 447 } 448 return parent != null && Files.isWritable(parent); 449 } 450 451 /** 452 * Open the set of output files, based on the configured 453 * instance variables. 454 */ 455 private void openFiles() throws IOException { 456 LogManager manager = LogManager.getLogManager(); 457 manager.checkPermission(); 458 if (count < 1) { 459 throw new IllegalArgumentException("file count = " + count); 460 } 461 if (limit < 0) { 462 limit = 0; 463 } 464 465 // All constructors check that pattern is neither null nor empty. 466 assert pattern != null : "pattern should not be null"; 467 assert !pattern.isEmpty() : "pattern should not be empty"; 468 469 // We register our own ErrorManager during initialization 470 // so we can record exceptions. 471 InitializationErrorManager em = new InitializationErrorManager(); 472 setErrorManager(em); 473 474 // Create a lock file. This grants us exclusive access 475 // to our set of output files, as long as we are alive. 476 int unique = -1; 477 for (;;) { 478 unique++; 479 if (unique > MAX_LOCKS) { 480 throw new IOException("Couldn't get lock for " + pattern); 481 } 482 // Generate a lock file name from the "unique" int. 483 lockFileName = generate(pattern, 0, unique).toString() + ".lck"; 484 // Now try to lock that filename. 485 // Because some systems (e.g., Solaris) can only do file locks 486 // between processes (and not within a process), we first check 487 // if we ourself already have the file locked. 488 synchronized(locks) { 489 if (locks.contains(lockFileName)) { 490 // We already own this lock, for a different FileHandler 491 // object. Try again. 492 continue; 493 } 494 495 final Path lockFilePath = Paths.get(lockFileName); 496 FileChannel channel = null; 497 int retries = -1; 498 boolean fileCreated = false; 499 while (channel == null && retries++ < 1) { 500 try { 501 channel = FileChannel.open(lockFilePath, 502 CREATE_NEW, WRITE); 503 fileCreated = true; 504 } catch (FileAlreadyExistsException ix) { 505 // This may be a zombie file left over by a previous 506 // execution. Reuse it - but only if we can actually 507 // write to its directory. 508 // Note that this is a situation that may happen, 509 // but not too frequently. 510 if (Files.isRegularFile(lockFilePath, LinkOption.NOFOLLOW_LINKS) 511 && isParentWritable(lockFilePath)) { 512 try { 513 channel = FileChannel.open(lockFilePath, 514 WRITE, APPEND); 515 } catch (NoSuchFileException x) { 516 // Race condition - retry once, and if that 517 // fails again just try the next name in 518 // the sequence. 519 continue; 520 } catch(IOException x) { 521 // the file may not be writable for us. 522 // try the next name in the sequence 523 break; 524 } 525 } else { 526 // at this point channel should still be null. 527 // break and try the next name in the sequence. 528 break; 529 } 530 } 531 } 532 533 if (channel == null) continue; // try the next name; 534 lockFileChannel = channel; 535 536 boolean available; 537 try { 538 available = lockFileChannel.tryLock() != null; 539 // We got the lock OK. 540 // At this point we could call File.deleteOnExit(). 541 // However, this could have undesirable side effects 542 // as indicated by JDK-4872014. So we will instead 543 // rely on the fact that close() will remove the lock 544 // file and that whoever is creating FileHandlers should 545 // be responsible for closing them. 546 } catch (IOException ix) { 547 // We got an IOException while trying to get the lock. 548 // This normally indicates that locking is not supported 549 // on the target directory. We have to proceed without 550 // getting a lock. Drop through, but only if we did 551 // create the file... 552 available = fileCreated; 553 } catch (OverlappingFileLockException x) { 554 // someone already locked this file in this VM, through 555 // some other channel - that is - using something else 556 // than new FileHandler(...); 557 // continue searching for an available lock. 558 available = false; 559 } 560 if (available) { 561 // We got the lock. Remember it. 562 locks.add(lockFileName); 563 break; 564 } 565 566 // We failed to get the lock. Try next file. 567 lockFileChannel.close(); 568 } 569 } 570 571 files = new File[count]; 572 for (int i = 0; i < count; i++) { 573 files[i] = generate(pattern, i, unique); 574 } 575 576 // Create the initial log file. 577 if (append) { 578 open(files[0], true); 579 } else { 580 rotate(); 581 } 582 583 // Did we detect any exceptions during initialization? 584 Exception ex = em.lastException; 585 if (ex != null) { 586 if (ex instanceof IOException) { 587 throw (IOException) ex; 588 } else if (ex instanceof SecurityException) { 589 throw (SecurityException) ex; 590 } else { 591 throw new IOException("Exception: " + ex); 592 } 593 } 594 595 // Install the normal default ErrorManager. 596 setErrorManager(new ErrorManager()); 597 } 598 599 /** 600 * Generate a file based on a user-supplied pattern, generation number, 601 * and an integer uniqueness suffix 602 * @param pattern the pattern for naming the output file 603 * @param generation the generation number to distinguish rotated logs 604 * @param unique a unique number to resolve conflicts 605 * @return the generated File 606 * @throws IOException 607 */ 608 private File generate(String pattern, int generation, int unique) 609 throws IOException { 610 File file = null; 611 String word = ""; 612 int ix = 0; 613 boolean sawg = false; 614 boolean sawu = false; 615 while (ix < pattern.length()) { 616 char ch = pattern.charAt(ix); 617 ix++; 618 char ch2 = 0; 619 if (ix < pattern.length()) { 620 ch2 = Character.toLowerCase(pattern.charAt(ix)); 621 } 622 if (ch == '/') { 623 if (file == null) { 624 file = new File(word); 625 } else { 626 file = new File(file, word); 627 } 628 word = ""; 629 continue; 630 } else if (ch == '%') { 631 if (ch2 == 't') { 632 String tmpDir = System.getProperty("java.io.tmpdir"); 633 if (tmpDir == null) { 634 tmpDir = System.getProperty("user.home"); 635 } 636 file = new File(tmpDir); 637 ix++; 638 word = ""; 639 continue; 640 } else if (ch2 == 'h') { 641 file = new File(System.getProperty("user.home")); 642 if (sun.misc.VM.isSetUID()) { 643 // Ok, we are in a set UID program. For safety's sake 644 // we disallow attempts to open files relative to %h. 645 throw new IOException("can't use %h in set UID program"); 646 } 647 ix++; 648 word = ""; 649 continue; 650 } else if (ch2 == 'g') { 651 word = word + generation; 652 sawg = true; 653 ix++; 654 continue; 655 } else if (ch2 == 'u') { 656 word = word + unique; 657 sawu = true; 658 ix++; 659 continue; 660 } else if (ch2 == '%') { 661 word = word + "%"; 662 ix++; 663 continue; 664 } 665 } 666 word = word + ch; 667 } 668 if (count > 1 && !sawg) { 669 word = word + "." + generation; 670 } 671 if (unique > 0 && !sawu) { 672 word = word + "." + unique; 673 } 674 if (word.length() > 0) { 675 if (file == null) { 676 file = new File(word); 677 } else { 678 file = new File(file, word); 679 } 680 } 681 return file; 682 } 683 684 /** 685 * Rotate the set of output files 686 */ 687 private synchronized void rotate() { 688 Level oldLevel = getLevel(); 689 setLevel(Level.OFF); 690 691 super.close(); 692 for (int i = count-2; i >= 0; i--) { 693 File f1 = files[i]; 694 File f2 = files[i+1]; 695 if (f1.exists()) { 696 if (f2.exists()) { 697 f2.delete(); 698 } 699 f1.renameTo(f2); 700 } 701 } 702 try { 703 open(files[0], false); 704 } catch (IOException ix) { 705 // We don't want to throw an exception here, but we 706 // report the exception to any registered ErrorManager. 707 reportError(null, ix, ErrorManager.OPEN_FAILURE); 708 709 } 710 setLevel(oldLevel); 711 } 712 713 /** 714 * Format and publish a {@code LogRecord}. 715 * 716 * @param record description of the log event. A null record is 717 * silently ignored and is not published 718 */ 719 @Override 720 public synchronized void publish(LogRecord record) { 721 if (!isLoggable(record)) { 722 return; 723 } 724 super.publish(record); 725 flush(); 726 if (limit > 0 && (meter.written >= limit || meter.written < 0)) { 727 // We performed access checks in the "init" method to make sure 728 // we are only initialized from trusted code. So we assume 729 // it is OK to write the target files, even if we are 730 // currently being called from untrusted code. 731 // So it is safe to raise privilege here. 732 AccessController.doPrivileged(new PrivilegedAction<Object>() { 733 @Override 734 public Object run() { 735 rotate(); 736 return null; 737 } 738 }); 739 } 740 } 741 742 /** 743 * Close all the files. 744 * 745 * @exception SecurityException if a security manager exists and if 746 * the caller does not have {@code LoggingPermission("control")}. 747 */ 748 @Override 749 public synchronized void close() throws SecurityException { 750 super.close(); 751 // Unlock any lock file. 752 if (lockFileName == null) { 753 return; 754 } 755 try { 756 // Close the lock file channel (which also will free any locks) 757 lockFileChannel.close(); 758 } catch (Exception ex) { 759 // Problems closing the stream. Punt. 760 } 761 synchronized(locks) { 762 locks.remove(lockFileName); 763 } 764 new File(lockFileName).delete(); 765 lockFileName = null; 766 lockFileChannel = null; 767 } 768 769 private static class InitializationErrorManager extends ErrorManager { 770 Exception lastException; 771 @Override 772 public void error(String msg, Exception ex, int code) { 773 lastException = ex; 774 } 775 } 776 }