Save This Page
Home » openjdk-7 » java » util » logging » [javadoc | source]
    1   /*
    2    * Copyright 2000-2006 Sun Microsystems, Inc.  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.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any 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<String, String>();
  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   }

Save This Page
Home » openjdk-7 » java » util » logging » [javadoc | source]