FileDocCategorySizeDatePackage
FileHandler.javaAPI DocAndroid 1.5 API25541Wed May 06 22:41:04 BST 2009java.util.logging

FileHandler

public class FileHandler extends StreamHandler
A {@code FileHandler} writes logging records into a specified file or a rotating set of files.

When a set of files is used and a given amount of data has been written to one file, then this file is closed and another file is opened. The name of these files are generated by given name pattern, see below for details.

By default, the I/O buffering mechanism is enabled, but when each log record is complete, it is flushed out.

{@code XMLFormatter} is the default formatter for {@code FileHandler}.

{@code FileHandler} reads the following {@code LogManager} properties for initialization; if a property is not defined or has an invalid value, a default value is used.

  • java.util.logging.FileHandler.level specifies the level for this {@code Handler}, defaults to {@code Level.ALL}.
  • java.util.logging.FileHandler.filter specifies the {@code Filter} class name, defaults to no {@code Filter}.
  • java.util.logging.FileHandler.formatter specifies the {@code Formatter} class, defaults to {@code java.util.logging.XMLFormatter}.
  • java.util.logging.FileHandler.encoding specifies the character set encoding name, defaults to the default platform encoding.
  • java.util.logging.FileHandler.limit specifies the maximum number of bytes to write to any one file, defaults to zero, which means no limit.
  • java.util.logging.FileHandler.count specifies how many output files to rotate, defaults to 1.
  • java.util.logging.FileHandler.pattern specifies name pattern for the output files. See below for details. Defaults to "%h/java%u.log".
  • java.util.logging.FileHandler.append specifies whether this {@code FileHandler} should append onto existing files, defaults to {@code false}.

Name pattern is a string that may include some special substrings, which will be replaced to generate output files:

  • "/" represents the local pathname separator
  • "%t" represents the system's temporary directory
  • "%h" represents the home directory of the current user, which is specified by "user.home" system property
  • "%g" represents the generation number to distinguish rotated logs
  • "%u" represents a unique number to resolve conflicts
  • "%%" represents the percent sign character '%'

Normally, the generation numbers are not larger than the given file count and follow the sequence 0, 1, 2.... If the file count is larger than one, but the generation field("%g") has not been specified in the pattern, then the generation number after a dot will be added to the end of the file name.

The "%u" unique field is used to avoid conflicts and is set to 0 at first. If one {@code FileHandler} tries to open the filename which is currently in use by another process, it will repeatedly increment the unique number field and try again. If the "%u" component has not been included in the file name pattern and some contention on a file does occur, then a unique numerical value will be added to the end of the filename in question immediately to the right of a dot. The generation of unique IDs for avoiding conflicts is only guaranteed to work reliably when using a local disk file system.

since
Android 1.0

Fields Summary
private static final String
LCK_EXT
private static final int
DEFAULT_COUNT
private static final int
DEFAULT_LIMIT
private static final boolean
DEFAULT_APPEND
private static final String
DEFAULT_PATTERN
private static final Hashtable
allLocks
private int
count
private int
limit
private boolean
append
private String
pattern
private LogManager
manager
private MeasureOutputStream
output
private File[]
files
FileLock
lock
String
fileName
int
uniqueID
Constructors Summary
public FileHandler()
Construct a {@code FileHandler} using {@code LogManager} properties or their default value.

throws
IOException if any I/O error occurs.
throws
SecurityException if a security manager exists and it determines that the caller does not have the required permissions to control this handler; required permissions include {@code LogPermission("control")}, {@code FilePermission("write")} etc.
since
Android 1.0


                                                                                                                                       
        
        init(null, null, null, null);
    
public FileHandler(String pattern)
Constructs a new {@code FileHandler}. The given name pattern is used as output filename, the file limit is set to zero (no limit), the file count is set to one; the remaining configuration is done using {@code LogManager} properties or their default values. This handler write to only one file without size limit.

param
pattern the name pattern for the output file.
throws
IOException if any I/O error occurs.
throws
SecurityException if a security manager exists and it determines that the caller does not have the required permissions to control this handler; required permissions include {@code LogPermission("control")}, {@code FilePermission("write")} etc.
throws
IllegalArgumentException if the pattern is empty.
throws
NullPointerException if the pattern is {@code null}.
since
Android 1.0

        if (pattern.equals("")) { //$NON-NLS-1$
            // logging.19=Pattern cannot be empty
            throw new IllegalArgumentException(Messages.getString("logging.19")); //$NON-NLS-1$
        }
        init(pattern, null, Integer.valueOf(DEFAULT_LIMIT), Integer
                .valueOf(DEFAULT_COUNT));
    
public FileHandler(String pattern, boolean append)
Construct a new {@code FileHandler}. The given name pattern is used as output filename, the file limit is set to zero (no limit), the file count is initialized to one and the value of {@code append} becomes the new instance's append mode. The remaining configuration is done using {@code LogManager} properties. This handler write to only one file without size limit.

param
pattern the name pattern for the output file.
param
append the append mode.
throws
IOException if any I/O error occurs.
throws
SecurityException if a security manager exists and it determines that the caller does not have the required permissions to control this handler; required permissions include {@code LogPermission("control")}, {@code FilePermission("write")} etc.
throws
IllegalArgumentException if {@code pattern} is empty.
throws
NullPointerException if {@code pattern} is {@code null}.
since
Android 1.0

        if (pattern.equals("")) { //$NON-NLS-1$
            throw new IllegalArgumentException(Messages.getString("logging.19")); //$NON-NLS-1$ 
        }

        init(pattern, Boolean.valueOf(append), Integer.valueOf(DEFAULT_LIMIT),
                Integer.valueOf(DEFAULT_COUNT));
    
public FileHandler(String pattern, int limit, int count)
Construct a new {@code FileHandler}. The given name pattern is used as output filename, the maximum file size is set to {@code limit} and the file count is initialized to {@code count}. The remaining configuration is done using {@code LogManager} properties. This handler is configured to write to a rotating set of count files, when the limit of bytes has been written to one output file, another file will be opened instead.

param
pattern the name pattern for the output file.
param
limit the data amount limit in bytes of one output file, can not be negative.
param
count the maximum number of files to use, can not be less than one.
throws
IOException if any I/O error occurs.
throws
SecurityException if a security manager exists and it determines that the caller does not have the required permissions to control this handler; required permissions include {@code LogPermission("control")}, {@code FilePermission("write")} etc.
throws
IllegalArgumentException if {@code pattern} is empty, {@code limit < 0} or {@code count < 1}.
throws
NullPointerException if {@code pattern} is {@code null}.
since
Android 1.0

        if (pattern.equals("")) { //$NON-NLS-1$
            throw new IllegalArgumentException(Messages.getString("logging.19")); //$NON-NLS-1$ 
        }
        if (limit < 0 || count < 1) {
            // logging.1B=The limit and count property must be larger than 0 and
            // 1, respectively
            throw new IllegalArgumentException(Messages.getString("logging.1B")); //$NON-NLS-1$
        }
        init(pattern, null, Integer.valueOf(limit), Integer.valueOf(count));
    
public FileHandler(String pattern, int limit, int count, boolean append)
Construct a new {@code FileHandler}. The given name pattern is used as output filename, the maximum file size is set to {@code limit}, the file count is initialized to {@code count} and the append mode is set to {@code append}. The remaining configuration is done using {@code LogManager} properties. This handler is configured to write to a rotating set of count files, when the limit of bytes has been written to one output file, another file will be opened instead.

param
pattern the name pattern for the output file.
param
limit the data amount limit in bytes of one output file, can not be negative.
param
count the maximum number of files to use, can not be less than one.
param
append the append mode.
throws
IOException if any I/O error occurs.
throws
SecurityException if a security manager exists and it determines that the caller does not have the required permissions to control this handler; required permissions include {@code LogPermission("control")}, {@code FilePermission("write")} etc.
throws
IllegalArgumentException if {@code pattern} is empty, {@code limit < 0} or {@code count < 1}.
throws
NullPointerException if {@code pattern} is {@code null}.
since
Android 1.0

        if (pattern.equals("")) { //$NON-NLS-1$
            throw new IllegalArgumentException(Messages.getString("logging.19")); //$NON-NLS-1$ 
        }
        if (limit < 0 || count < 1) {
            // logging.1B=The limit and count property must be larger than 0 and
            // 1, respectively
            throw new IllegalArgumentException(Messages.getString("logging.1B")); //$NON-NLS-1$
        }
        init(pattern, Boolean.valueOf(append), Integer.valueOf(limit), Integer
                .valueOf(count));
    
Methods Summary
public voidclose()
Flushes and closes all opened files.

throws
SecurityException if a security manager exists and it determines that the caller does not have the required permissions to control this handler; required permissions include {@code LogPermission("control")}, {@code FilePermission("write")} etc.
since
Android 1.0

        // release locks
        super.close();
        allLocks.remove(fileName);
        try {
            FileChannel channel = lock.channel();
            lock.release();
            channel.close();
            File file = new File(fileName + LCK_EXT);
            file.delete();
        } catch (IOException e) {
            // ignore
        }
    
voidfindNextGeneration()

        super.close();
        for (int i = count - 1; i > 0; i--) {
            if (files[i].exists()) {
                files[i].delete();
            }
            files[i - 1].renameTo(files[i]);
        }
        try {
            // BEGIN android-modified
            output = new MeasureOutputStream(
                    new BufferedOutputStream(
                            new FileOutputStream(files[0]),
                            8192));
            // END android-modified
        } catch (FileNotFoundException e1) {
            // logging.1A=Error happened when open log file.
            this.getErrorManager().error(Messages.getString("logging.1A"), //$NON-NLS-1$
                    e1, ErrorManager.OPEN_FAILURE);
        }
        setOutputStream(output);
    
private booleangetBooleanProperty(java.lang.String key, boolean defaultValue)

        String property = manager.getProperty(key);
        if (null == property) {
            return defaultValue;
        }
        boolean result = defaultValue;
        if ("true".equalsIgnoreCase(property)) { //$NON-NLS-1$
            result = true;
        } else if ("false".equalsIgnoreCase(property)) { //$NON-NLS-1$
            result = false;
        }
        return result;
    
private intgetIntProperty(java.lang.String key, int defaultValue)

        String property = manager.getProperty(key);
        int result = defaultValue;
        if (null != property) {
            try {
                result = Integer.parseInt(property);
            } catch (Exception e) {
                // ignore
            }
        }
        return result;
    
private java.lang.StringgetStringProperty(java.lang.String key, java.lang.String defaultValue)

        String property = manager.getProperty(key);
        return property == null ? defaultValue : property;
    
private voidinit(java.lang.String p, java.lang.Boolean a, java.lang.Integer l, java.lang.Integer c)

        // check access
        manager = LogManager.getLogManager();
        manager.checkAccess();
        initProperties(p, a, l, c);
        initOutputFiles();
    
private voidinitOutputFiles()

        while (true) {
            // try to find a unique file which is not locked by other process
            uniqueID++;
            // FIXME: improve performance here
            for (int generation = 0; generation < count; generation++) {
                // cache all file names for rotation use
                files[generation] = new File(parseFileName(generation));
            }
            fileName = files[0].getAbsolutePath();
            synchronized (allLocks) {
                /*
                 * if current process has held lock for this fileName continue
                 * to find next file
                 */
                if (null != allLocks.get(fileName)) {
                    continue;
                }
                if (files[0].exists()
                        && (!append || files[0].length() >= limit)) {
                    for (int i = count - 1; i > 0; i--) {
                        if (files[i].exists()) {
                            files[i].delete();
                        }
                        files[i - 1].renameTo(files[i]);
                    }
                }
                FileOutputStream fileStream = new FileOutputStream(fileName
                        + LCK_EXT);
                FileChannel channel = fileStream.getChannel();
                /*
                 * if lock is unsupported and IOException thrown, just let the
                 * IOException throws out and exit otherwise it will go into an
                 * undead cycle
                 */
                lock = channel.tryLock();
                if (null == lock) {
                    try {
                        fileStream.close();
                    } catch (Exception e) {
                        // ignore
                    }
                    continue;
                }
                allLocks.put(fileName, lock);
                break;
            }
        }
        // BEGIN android-modified
        output = new MeasureOutputStream(
                new BufferedOutputStream(
                        new FileOutputStream(fileName, append), 8192),
                files[0].length());
        // END android-modified
        setOutputStream(output);
    
private voidinitProperties(java.lang.String p, java.lang.Boolean a, java.lang.Integer l, java.lang.Integer c)

        super.initProperties("ALL", null, "java.util.logging.XMLFormatter", //$NON-NLS-1$//$NON-NLS-2$
                null);
        String className = this.getClass().getName();
        pattern = (null == p) ? getStringProperty(className + ".pattern", //$NON-NLS-1$
                DEFAULT_PATTERN) : p;
        if (null == pattern || "".equals(pattern)) { //$NON-NLS-1$
            // logging.19=Pattern cannot be empty
            throw new NullPointerException(Messages.getString("logging.19")); //$NON-NLS-1$
        }
        append = (null == a) ? getBooleanProperty(className + ".append", //$NON-NLS-1$
                DEFAULT_APPEND) : a.booleanValue();
        count = (null == c) ? getIntProperty(className + ".count", //$NON-NLS-1$
                DEFAULT_COUNT) : c.intValue();
        limit = (null == l) ? getIntProperty(className + ".limit", //$NON-NLS-1$
                DEFAULT_LIMIT) : l.intValue();
        count = count < 1 ? DEFAULT_COUNT : count;
        limit = limit < 0 ? DEFAULT_LIMIT : limit;
        files = new File[count];
    
private java.lang.StringparseFileName(int gen)
Transform the pattern to the valid file name, replacing any patterns, and applying generation and uniqueID if present.

param
gen generation of this file
return
transformed filename ready for use.

        int cur = 0;
        int next = 0;
        boolean hasUniqueID = false;
        boolean hasGeneration = false;

        // TODO privilege code?

        String tempPath = System.getProperty("java.io.tmpdir"); //$NON-NLS-1$
        boolean tempPathHasSepEnd = (tempPath == null ? false : tempPath
                .endsWith(File.separator));

        String homePath = System.getProperty("user.home"); //$NON-NLS-1$
        boolean homePathHasSepEnd = (homePath == null ? false : homePath
                .endsWith(File.separator));

        StringBuilder sb = new StringBuilder();
        pattern = pattern.replace('/", File.separatorChar);

        char[] value = pattern.toCharArray();
        while ((next = pattern.indexOf('%", cur)) >= 0) {
            if (++next < pattern.length()) {
                switch (value[next]) {
                    case 'g":
                        sb.append(value, cur, next - cur - 1).append(gen);
                        hasGeneration = true;
                        break;
                    case 'u":
                        sb.append(value, cur, next - cur - 1).append(uniqueID);
                        hasUniqueID = true;
                        break;
                    case 't":
                        /*
                         * we should probably try to do something cute here like
                         * lookahead for adjacent '/'
                         */
                        sb.append(value, cur, next - cur - 1).append(tempPath);
                        if (!tempPathHasSepEnd) {
                            sb.append(File.separator);
                        }
                        break;
                    case 'h":
                        sb.append(value, cur, next - cur - 1).append(homePath);
                        if (!homePathHasSepEnd) {
                            sb.append(File.separator);
                        }
                        break;
                    case '%":
                        sb.append(value, cur, next - cur - 1).append('%");
                        break;
                    default:
                        sb.append(value, cur, next - cur);
                }
                cur = ++next;
            } else {
                // fail silently
            }
        }

        sb.append(value, cur, value.length - cur);

        if (!hasGeneration && count > 1) {
            sb.append(".").append(gen); //$NON-NLS-1$
        }

        if (!hasUniqueID && uniqueID > 0) {
            sb.append(".").append(uniqueID); //$NON-NLS-1$
        }

        return sb.toString();
    
public voidpublish(java.util.logging.LogRecord record)
Publish a {@code LogRecord}.

param
record the log record to publish.
since
Android 1.0

        super.publish(record);
        flush();
        if (limit > 0 && output.getLength() >= limit) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    findNextGeneration();
                    return null;
                }
            });
        }