FileHandlerpublic 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.
|
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.
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.
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.
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.
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.
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 void | close()Flushes and closes all opened files.
// 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
}
| void | findNextGeneration()
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 boolean | getBooleanProperty(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 int | getIntProperty(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.String | getStringProperty(java.lang.String key, java.lang.String defaultValue)
String property = manager.getProperty(key);
return property == null ? defaultValue : property;
| private void | init(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 void | initOutputFiles()
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 void | initProperties(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.String | parseFileName(int gen)Transform the pattern to the valid file name, replacing any patterns, and
applying generation and uniqueID if present.
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 void | publish(java.util.logging.LogRecord record)Publish a {@code LogRecord}.
super.publish(record);
flush();
if (limit > 0 && output.getLength() >= limit) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
findNextGeneration();
return null;
}
});
}
|
|