001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.util.logging;
019:
020: import java.io.BufferedOutputStream;
021: import java.io.File;
022: import java.io.FileNotFoundException;
023: import java.io.FileOutputStream;
024: import java.io.IOException;
025: import java.io.OutputStream;
026: import java.nio.channels.FileChannel;
027: import java.nio.channels.FileLock;
028: import java.security.AccessController;
029: import java.security.PrivilegedAction;
030: import java.util.Hashtable;
031:
032: import org.apache.harmony.logging.internal.nls.Messages;
033:
034: /**
035: * A <code>FileHandler</code> is a Handler that writes logging events to one
036: * or more files.
037: *
038: * <p>
039: * If multiple files are used, when a given amount of data has been written to
040: * one file, this file is closed, and the next file is opened. The names of
041: * these files are generated by the given name pattern, see below for details.
042: * When all the files have all been filled the Handler returns to the first one
043: * and goes through the set again.
044: * </p>
045: *
046: * <p>
047: * <code>FileHandler</code> defines the following configuration properties,
048: * which are read by the <code>LogManager</code> on initialization. If the
049: * properties have not been specified then defaults will be used. The properties
050: * and defaults are as follows:
051: * <ul>
052: * <li>java.util.logging.FileHandler.append - If true then this
053: * <code>FileHandler</code> appends to a file's existing content, if false it
054: * overwrites it. Default is false.</li>
055: * <li>java.util.logging.FileHandler.count - the number of output files to
056: * rotate. Default is 1.</li>
057: * <li>java.util.logging.FileHandler.filter - the name of the
058: * <code>Filter</code> class. No <code>Filter</code> is used by default.</li>
059: * <li>java.util.logging.FileHandler.formatter - the name of the
060: * <code>Formatter</code> class. Default is
061: * <code>java.util.logging.XMLFormatter</code>.</li>
062: * <li>java.util.logging.FileHandler.encoding - the name of the character set
063: * encoding. Default is the encoding used by the current platform.</li>
064: * <li>java.util.logging.FileHandler.level - the log level for this
065: * <code>Handler</code>. Default is <code>Level.ALL</code>.</li>
066: * <li>java.util.logging.FileHandler.limit - the limit at which no more bytes
067: * should be written to the current file. Default is no limit.</li>
068: * <li>java.util.logging.FileHandler.pattern - the pattern for the name of log
069: * files. Default is "%h/java%u.log".</li>
070: * </ul>
071: * </p>
072: *
073: * <p>
074: * The name pattern is a String that can contain some of the following
075: * sub-strings, which will be replaced to generate the output file names:
076: * <ul>
077: * <li>"/" represents the local path separator</li>
078: * <li>"%g" represents the generation number used to enumerate log files</li>
079: * <li>"%h" represents the home directory of the current user, which is
080: * specified by the "user.home" system property</li>
081: * <li>"%t" represents the system's temporary directory</li>
082: * <li>"%u" represents a unique number added to the file name if the original
083: * file required is in use</li>
084: * <li>"%%" represents the percent sign character '%'</li>
085: * </ul>
086: * </p>
087: *
088: * <p>
089: * The generation numbers, denoted by "%g" in the filename pattern will be
090: * created in ascending numerical order from 0, i.e. 0,1,2,3... If "%g" was not
091: * present in the pattern and more than one file is being used then a dot and a
092: * generation number is appended to the filename at the end. This is equivalent
093: * to appending ".%g" to the pattern.
094: * </p>
095: *
096: * <p>
097: * The unique identifier, denoted by "%u" in the filename pattern will always be
098: * 0 unless the <code>FileHandler</code> is unable to open the file. In that
099: * case 1 is tried, then 2, and so on until a file is found that can be opened.
100: * If "%u" was not present in the pattern but a unique number is required then a
101: * dot and a unique number is added to the end of the filename, equivalent to
102: * appending ".%u" to the pattern.
103: * </p>
104: */
105: public class FileHandler extends StreamHandler {
106:
107: private static final String LCK_EXT = ".lck"; //$NON-NLS-1$
108:
109: private static final int DEFAULT_COUNT = 1;
110:
111: private static final int DEFAULT_LIMIT = 0;
112:
113: private static final boolean DEFAULT_APPEND = false;
114:
115: private static final String DEFAULT_PATTERN = "%h/java%u.log"; //$NON-NLS-1$
116:
117: // maintain all file locks hold by this process
118: private static final Hashtable<String, FileLock> allLocks = new Hashtable<String, FileLock>();
119:
120: // the count of files which the output cycle through
121: private int count;
122:
123: // the size limitation in byte of log file
124: private int limit;
125:
126: // whether the FileHandler should open a existing file for output in append
127: // mode
128: private boolean append;
129:
130: // the pattern for output file name
131: private String pattern;
132:
133: // maintain a LogManager instance for convenience
134: private LogManager manager;
135:
136: // output stream, which can measure the output file length
137: private MeasureOutputStream output;
138:
139: // used output file
140: private File[] files;
141:
142: // output file lock
143: FileLock lock = null;
144:
145: // current output file name
146: String fileName = null;
147:
148: // current unique ID
149: int uniqueID = -1;
150:
151: /**
152: * Construct a <code>FileHandler</code> using <code>LogManager</code>
153: * properties or their default value
154: *
155: * @throws IOException
156: * if any IO exception happened
157: * @throws SecurityException
158: * if security manager exists and it determines that caller does
159: * not have the required permissions to control this handler,
160: * required permissions include
161: * <code>LogPermission("control")</code> and other permission
162: * like <code>FilePermission("write")</code>, etc.
163: */
164: public FileHandler() throws IOException {
165: init(null, null, null, null);
166: }
167:
168: // init properties
169: private void init(String p, Boolean a, Integer l, Integer c)
170: throws IOException {
171: // check access
172: manager = LogManager.getLogManager();
173: manager.checkAccess();
174: initProperties(p, a, l, c);
175: initOutputFiles();
176: }
177:
178: private void initOutputFiles() throws FileNotFoundException,
179: IOException {
180: while (true) {
181: // try to find a unique file which is not locked by other process
182: uniqueID++;
183: // FIXME: improve performance here
184: for (int generation = 0; generation < count; generation++) {
185: // cache all file names for rotation use
186: files[generation] = new File(parseFileName(generation));
187: }
188: fileName = files[0].getAbsolutePath();
189: synchronized (allLocks) {
190: /*
191: * if current process has held lock for this fileName continue
192: * to find next file
193: */
194: if (null != allLocks.get(fileName)) {
195: continue;
196: }
197: if (files[0].exists()
198: && (!append || files[0].length() >= limit)) {
199: for (int i = count - 1; i > 0; i--) {
200: if (files[i].exists()) {
201: files[i].delete();
202: }
203: files[i - 1].renameTo(files[i]);
204: }
205: }
206: FileOutputStream fileStream = new FileOutputStream(
207: fileName + LCK_EXT);
208: FileChannel channel = fileStream.getChannel();
209: /*
210: * if lock is unsupported and IOException thrown, just let the
211: * IOException throws out and exit otherwise it will go into an
212: * undead cycle
213: */
214: lock = channel.tryLock();
215: if (null == lock) {
216: try {
217: fileStream.close();
218: } catch (Exception e) {
219: // ignore
220: }
221: continue;
222: }
223: allLocks.put(fileName, lock);
224: break;
225: }
226: }
227: output = new MeasureOutputStream(new BufferedOutputStream(
228: new FileOutputStream(fileName, append)), files[0]
229: .length());
230: setOutputStream(output);
231: }
232:
233: @SuppressWarnings("nls")
234: private void initProperties(String p, Boolean a, Integer l,
235: Integer c) {
236: super .initProperties("ALL", null,
237: "java.util.logging.XMLFormatter", null);
238: String className = this .getClass().getName();
239: pattern = (null == p) ? getStringProperty(className
240: + ".pattern", DEFAULT_PATTERN) : p;
241: if (null == pattern || "".equals(pattern)) {
242: // logging.19=Pattern cannot be empty
243: throw new NullPointerException(Messages
244: .getString("logging.19"));
245: }
246: append = (null == a) ? getBooleanProperty(
247: className + ".append", DEFAULT_APPEND) : a
248: .booleanValue();
249: count = (null == c) ? getIntProperty(className + ".count",
250: DEFAULT_COUNT) : c.intValue();
251: limit = (null == l) ? getIntProperty(className + ".limit",
252: DEFAULT_LIMIT) : l.intValue();
253: count = count < 1 ? DEFAULT_COUNT : count;
254: limit = limit < 0 ? DEFAULT_LIMIT : limit;
255: files = new File[count];
256: }
257:
258: void findNextGeneration() {
259: super .close();
260: for (int i = count - 1; i > 0; i--) {
261: if (files[i].exists()) {
262: files[i].delete();
263: }
264: files[i - 1].renameTo(files[i]);
265: }
266: try {
267: output = new MeasureOutputStream(new BufferedOutputStream(
268: new FileOutputStream(files[0])));
269: } catch (FileNotFoundException e1) {
270: // logging.1A=Error happened when open log file.
271: this .getErrorManager().error(
272: Messages.getString("logging.1A"), //$NON-NLS-1$
273: e1, ErrorManager.OPEN_FAILURE);
274: }
275: setOutputStream(output);
276: }
277:
278: /**
279: * Transform the pattern to the valid file name, replacing any patterns, and
280: * applying generation and uniqueID if present
281: *
282: * @param gen
283: * generation of this file
284: * @return transformed filename ready for use
285: */
286: private String parseFileName(int gen) {
287: int cur = 0;
288: int next = 0;
289: boolean hasUniqueID = false;
290: boolean hasGeneration = false;
291:
292: // TODO privilege code?
293:
294: String tempPath = System.getProperty("java.io.tmpdir"); //$NON-NLS-1$
295: boolean tempPathHasSepEnd = (tempPath == null ? false
296: : tempPath.endsWith(File.separator));
297:
298: String homePath = System.getProperty("user.home"); //$NON-NLS-1$
299: boolean homePathHasSepEnd = (homePath == null ? false
300: : homePath.endsWith(File.separator));
301:
302: StringBuilder sb = new StringBuilder();
303: pattern = pattern.replace('/', File.separatorChar);
304:
305: char[] value = pattern.toCharArray();
306: while ((next = pattern.indexOf('%', cur)) >= 0) {
307: if (++next < pattern.length()) {
308: switch (value[next]) {
309: case 'g':
310: sb.append(value, cur, next - cur - 1).append(gen);
311: hasGeneration = true;
312: break;
313: case 'u':
314: sb.append(value, cur, next - cur - 1).append(
315: uniqueID);
316: hasUniqueID = true;
317: break;
318: case 't':
319: /*
320: * we should probably try to do something cute here like
321: * lookahead for adjacent '/'
322: */
323: sb.append(value, cur, next - cur - 1).append(
324: tempPath);
325: if (!tempPathHasSepEnd) {
326: sb.append(File.separator);
327: }
328: break;
329: case 'h':
330: sb.append(value, cur, next - cur - 1).append(
331: homePath);
332: if (!homePathHasSepEnd) {
333: sb.append(File.separator);
334: }
335: break;
336: case '%':
337: sb.append(value, cur, next - cur - 1).append('%');
338: break;
339: default:
340: sb.append(value, cur, next - cur);
341: }
342: cur = ++next;
343: } else {
344: // fail silently
345: }
346: }
347:
348: sb.append(value, cur, value.length - cur);
349:
350: if (!hasGeneration && count > 1) {
351: sb.append(".").append(gen); //$NON-NLS-1$
352: }
353:
354: if (!hasUniqueID && uniqueID > 0) {
355: sb.append(".").append(uniqueID); //$NON-NLS-1$
356: }
357:
358: return sb.toString();
359: }
360:
361: // get boolean LogManager property, if invalid value got, using default
362: // value
363: private boolean getBooleanProperty(String key, boolean defaultValue) {
364: String property = manager.getProperty(key);
365: if (null == property) {
366: return defaultValue;
367: }
368: boolean result = defaultValue;
369: if ("true".equalsIgnoreCase(property)) { //$NON-NLS-1$
370: result = true;
371: } else if ("false".equalsIgnoreCase(property)) { //$NON-NLS-1$
372: result = false;
373: }
374: return result;
375: }
376:
377: // get String LogManager property, if invalid value got, using default value
378: private String getStringProperty(String key, String defaultValue) {
379: String property = manager.getProperty(key);
380: return property == null ? defaultValue : property;
381: }
382:
383: // get int LogManager property, if invalid value got, using default value
384: private int getIntProperty(String key, int defaultValue) {
385: String property = manager.getProperty(key);
386: int result = defaultValue;
387: if (null != property) {
388: try {
389: result = Integer.parseInt(property);
390: } catch (Exception e) {
391: // ignore
392: }
393: }
394: return result;
395: }
396:
397: /**
398: * Construct a <code>FileHandler</code>, the given name pattern is used
399: * as output filename, the file limit is set to zero(no limit), and the file
400: * count is set to one, other configuration using <code>LogManager</code>
401: * properties or their default value
402: *
403: * This handler write to only one file and no amount limit.
404: *
405: * @param pattern
406: * the name pattern of output file
407: * @throws IOException
408: * if any IO exception happened
409: * @throws SecurityException
410: * if security manager exists and it determines that caller does
411: * not have the required permissions to control this handler,
412: * required permissions include
413: * <code>LogPermission("control")</code> and other permission
414: * like <code>FilePermission("write")</code>, etc.
415: * @throws NullPointerException
416: * if the pattern is <code>null</code>.
417: * @throws IllegalArgumentException
418: * if the pattern is empty.
419: */
420: public FileHandler(String pattern) throws IOException {
421: if (pattern.equals("")) { //$NON-NLS-1$
422: // logging.19=Pattern cannot be empty
423: throw new IllegalArgumentException(Messages
424: .getString("logging.19")); //$NON-NLS-1$
425: }
426: init(pattern, null, Integer.valueOf(DEFAULT_LIMIT), Integer
427: .valueOf(DEFAULT_COUNT));
428: }
429:
430: /**
431: * Construct a <code>FileHandler</code>, the given name pattern is used
432: * as output filename, the file limit is set to zero(i.e. no limit applies),
433: * the file count is initialized to one, and the value of
434: * <code>append</code> becomes the new instance's append mode. Other
435: * configuration is done using <code>LogManager</code> properties.
436: *
437: * This handler write to only one file and no amount limit.
438: *
439: * @param pattern
440: * the name pattern of output file
441: * @param append
442: * the append mode
443: * @throws IOException
444: * if any IO exception happened
445: * @throws SecurityException
446: * if security manager exists and it determines that caller does
447: * not have the required permissions to control this handler,
448: * required permissions include
449: * <code>LogPermission("control")</code> and other permission
450: * like <code>FilePermission("write")</code>, etc.
451: * @throws NullPointerException
452: * if the pattern is <code>null</code>.
453: * @throws IllegalArgumentException
454: * if the pattern is empty.
455: */
456: public FileHandler(String pattern, boolean append)
457: throws IOException {
458: if (pattern.equals("")) { //$NON-NLS-1$
459: throw new IllegalArgumentException(Messages
460: .getString("logging.19")); //$NON-NLS-1$
461: }
462:
463: init(pattern, Boolean.valueOf(append), Integer
464: .valueOf(DEFAULT_LIMIT), Integer.valueOf(DEFAULT_COUNT));
465: }
466:
467: /**
468: * Construct a <code>FileHandler</code>, the given name pattern is used
469: * as output filename, the file limit is set to given limit argument, and
470: * the file count is set to given count argument, other configuration using
471: * <code>LogManager</code> properties or their default value
472: *
473: * This handler is configured to write to a rotating set of count files,
474: * when the limit of bytes has been written to one output file, another file
475: * will be opened instead.
476: *
477: * @param pattern
478: * the name pattern of output file
479: * @param limit
480: * the data amount limit in bytes of one output file, cannot less
481: * than one
482: * @param count
483: * the maximum number of files can be used, cannot less than one
484: * @throws IOException
485: * if any IO exception happened
486: * @throws SecurityException
487: * if security manager exists and it determines that caller does
488: * not have the required permissions to control this handler,
489: * required permissions include
490: * <code>LogPermission("control")</code> and other permission
491: * like <code>FilePermission("write")</code>, etc.
492: * @throws NullPointerException
493: * if pattern is <code>null</code>.
494: * @throws IllegalArgumentException
495: * if count<1, or limit<0
496: */
497: public FileHandler(String pattern, int limit, int count)
498: throws IOException {
499: if (pattern.equals("")) { //$NON-NLS-1$
500: throw new IllegalArgumentException(Messages
501: .getString("logging.19")); //$NON-NLS-1$
502: }
503: if (limit < 0 || count < 1) {
504: // logging.1B=The limit and count property must be larger than 0 and
505: // 1, respectively
506: throw new IllegalArgumentException(Messages
507: .getString("logging.1B")); //$NON-NLS-1$
508: }
509: init(pattern, null, Integer.valueOf(limit), Integer
510: .valueOf(count));
511: }
512:
513: /**
514: * Construct a <code>FileHandler</code>, the given name pattern is used
515: * as output filename, the file limit is set to given limit argument, the
516: * file count is set to given count argument, and the append mode is set to
517: * given append argument, other configuration using <code>LogManager</code>
518: * properties or their default value
519: *
520: * This handler is configured to write to a rotating set of count files,
521: * when the limit of bytes has been written to one output file, another file
522: * will be opened instead.
523: *
524: * @param pattern
525: * the name pattern of output file
526: * @param limit
527: * the data amount limit in bytes of one output file, cannot less
528: * than one
529: * @param count
530: * the maximum number of files can be used, cannot less than one
531: * @param append
532: * the append mode
533: * @throws IOException
534: * if any IO exception happened
535: * @throws SecurityException
536: * if security manager exists and it determines that caller does
537: * not have the required permissions to control this handler,
538: * required permissions include
539: * <code>LogPermission("control")</code> and other permission
540: * like <code>FilePermission("write")</code>, etc.
541: * @throws NullPointerException
542: * if pattern is <code>null</code>.
543: * @throws IllegalArgumentException
544: * if count<1, or limit<0
545: */
546: public FileHandler(String pattern, int limit, int count,
547: boolean append) throws IOException {
548: if (pattern.equals("")) { //$NON-NLS-1$
549: throw new IllegalArgumentException(Messages
550: .getString("logging.19")); //$NON-NLS-1$
551: }
552: if (limit < 0 || count < 1) {
553: // logging.1B=The limit and count property must be larger than 0 and
554: // 1, respectively
555: throw new IllegalArgumentException(Messages
556: .getString("logging.1B")); //$NON-NLS-1$
557: }
558: init(pattern, Boolean.valueOf(append), Integer.valueOf(limit),
559: Integer.valueOf(count));
560: }
561:
562: /**
563: * Flush and close all opened files.
564: *
565: * @throws SecurityException
566: * if security manager exists and it determines that caller does
567: * not have the required permissions to control this handler,
568: * required permissions include
569: * <code>LogPermission("control")</code> and other permission
570: * like <code>FilePermission("write")</code>, etc.
571: */
572: @Override
573: public void close() {
574: // release locks
575: super .close();
576: allLocks.remove(fileName);
577: try {
578: FileChannel channel = lock.channel();
579: lock.release();
580: channel.close();
581: File file = new File(fileName + LCK_EXT);
582: file.delete();
583: } catch (IOException e) {
584: // ignore
585: }
586: }
587:
588: /**
589: * Publish a <code>LogRecord</code>
590: *
591: * @param record
592: * the log record to be published
593: */
594: @Override
595: public void publish(LogRecord record) {
596: super .publish(record);
597: flush();
598: if (limit > 0 && output.getLength() >= limit) {
599: AccessController
600: .doPrivileged(new PrivilegedAction<Object>() {
601: public Object run() {
602: findNextGeneration();
603: return null;
604: }
605: });
606: }
607: }
608:
609: /**
610: * This output stream use decorator pattern to add measure feature to
611: * OutputStream which can detect the total size(in bytes) of output, the
612: * initial size can be set
613: */
614: static class MeasureOutputStream extends OutputStream {
615:
616: OutputStream wrapped;
617:
618: long length;
619:
620: public MeasureOutputStream(OutputStream stream,
621: long currentLength) {
622: wrapped = stream;
623: length = currentLength;
624: }
625:
626: public MeasureOutputStream(OutputStream stream) {
627: this (stream, 0);
628: }
629:
630: @Override
631: public void write(int oneByte) throws IOException {
632: wrapped.write(oneByte);
633: length++;
634: }
635:
636: @Override
637: public void write(byte[] bytes) throws IOException {
638: wrapped.write(bytes);
639: length += bytes.length;
640: }
641:
642: @Override
643: public void write(byte[] b, int off, int len)
644: throws IOException {
645: wrapped.write(b, off, len);
646: length += len;
647: }
648:
649: @Override
650: public void close() throws IOException {
651: wrapped.close();
652: }
653:
654: @Override
655: public void flush() throws IOException {
656: wrapped.flush();
657: }
658:
659: public long getLength() {
660: return length;
661: }
662:
663: public void setLength(long newLength) {
664: length = newLength;
665: }
666: }
667: }
|