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