| FileHandler.java |
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