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