KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > util > logging > FileHandler


1 /*
2  * @(#)FileHandler.java 1.34 04/04/05
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN 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 JavaDoc;
12 import java.nio.channels.FileLock JavaDoc;
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 1.34, 04/05/04
100  * @since 1.4
101  */

102
103 public class FileHandler extends StreamHandler JavaDoc {
104     private MeteredStream meter;
105     private boolean append;
106     private int limit; // zero => no limit.
107
private int count;
108     private String JavaDoc pattern;
109     private String JavaDoc lockFileName;
110     private FileOutputStream lockStream;
111     private File files[];
112     private static final int MAX_LOCKS = 100;
113     private static java.util.HashMap JavaDoc locks = new java.util.HashMap JavaDoc();
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 JavaDoc manager = LogManager.getLogManager();
167
168     String JavaDoc 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 JavaDoc()));
183     try {
184         setEncoding(manager.getStringProperty(cname +".encoding", null));
185     } catch (Exception JavaDoc ex) {
186         try {
187             setEncoding(null);
188         } catch (Exception JavaDoc 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 JavaDoc {
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 JavaDoc pattern) throws IOException, SecurityException JavaDoc {
229     if (pattern.length() < 1 ) {
230         throw new IllegalArgumentException JavaDoc();
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 JavaDoc pattern, boolean append) throws IOException, SecurityException JavaDoc {
261     if (pattern.length() < 1 ) {
262         throw new IllegalArgumentException JavaDoc();
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 JavaDoc pattern, int limit, int count)
297                     throws IOException, SecurityException JavaDoc {
298     if (limit < 0 || count < 1 || pattern.length() < 1) {
299         throw new IllegalArgumentException JavaDoc();
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 JavaDoc pattern, int limit, int count, boolean append)
336                     throws IOException, SecurityException JavaDoc {
337     if (limit < 0 || count < 1 || pattern.length() < 1) {
338         throw new IllegalArgumentException JavaDoc();
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 JavaDoc manager = LogManager.getLogManager();
353     manager.checkAccess();
354     if (count < 1) {
355        throw new IllegalArgumentException JavaDoc("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 JavaDoc 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 JavaDoc 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 JavaDoc ex = em.lastException;
428     if (ex != null) {
429         if (ex instanceof IOException) {
430         throw (IOException) ex;
431         } else if (ex instanceof SecurityException JavaDoc) {
432         throw (SecurityException JavaDoc) ex;
433         } else {
434         throw new IOException("Exception: " + ex);
435         }
436     }
437
438     // Install the normal default ErrorManager.
439
setErrorManager(new ErrorManager JavaDoc());
440     }
441
442     // Generate a filename from a pattern.
443
private File generate(String JavaDoc pattern, int generation, int unique) throws IOException {
444     File file = null;
445     String JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc {
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 JavaDoc 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 JavaDoc {
600     Exception JavaDoc lastException;
601     public void error(String JavaDoc msg, Exception JavaDoc 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
Popular Tags