Logback: the reliable, generic, fast and flexible logging framework.
Copyright (C) 1999-2015, QOS.ch. All rights reserved.
This program and the accompanying materials are dual-licensed under
either the terms of the Eclipse Public License v1.0 as published by
the Eclipse Foundation
or (per the licensee's choosing)
under the terms of the GNU Lesser General Public License version 2.1
as published by the Free Software Foundation.
/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2015, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.core;
import static ch.qos.logback.core.CoreConstants.CODES_URL;
import static ch.qos.logback.core.CoreConstants.MORE_INFO_PREFIX;
import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Map;
import java.util.Map.Entry;
import ch.qos.logback.core.recovery.ResilientFileOutputStream;
import ch.qos.logback.core.util.ContextUtil;
import ch.qos.logback.core.util.FileSize;
import ch.qos.logback.core.util.FileUtil;
FileAppender appends log events to a file.
For more information about this appender, please refer to the online manual
at http://logback.qos.ch/manual/appenders.html#FileAppender
Author: Ceki Gülcü
/**
* FileAppender appends log events to a file.
*
* For more information about this appender, please refer to the online manual
* at http://logback.qos.ch/manual/appenders.html#FileAppender
*
* @author Ceki Gülcü
*/
public class FileAppender<E> extends OutputStreamAppender<E> {
public static final long DEFAULT_BUFFER_SIZE = 8192;
static protected String COLLISION_WITH_EARLIER_APPENDER_URL = CODES_URL + "#earlier_fa_collision";
Append to or truncate the file? The default value for this variable is
true
, meaning that by default a FileAppender
will
append to an existing file and not truncate it.
/**
* Append to or truncate the file? The default value for this variable is
* <code>true</code>, meaning that by default a <code>FileAppender</code> will
* append to an existing file and not truncate it.
*/
protected boolean append = true;
The name of the active log file.
/**
* The name of the active log file.
*/
protected String fileName = null;
private boolean prudent = false;
private FileSize bufferSize = new FileSize(DEFAULT_BUFFER_SIZE);
The File property takes a string value which should be the name of
the file to append to.
/**
* The <b>File</b> property takes a string value which should be the name of
* the file to append to.
*/
public void setFile(String file) {
if (file == null) {
fileName = file;
} else {
// Trim spaces from both ends. The users probably does not want
// trailing spaces in file names.
fileName = file.trim();
}
}
Returns the value of the Append property.
/**
* Returns the value of the <b>Append</b> property.
*/
public boolean isAppend() {
return append;
}
This method is used by derived classes to obtain the raw file property.
Regular users should not be calling this method.
Returns: the value of the file property
/**
* This method is used by derived classes to obtain the raw file property.
* Regular users should not be calling this method.
*
* @return the value of the file property
*/
final public String rawFileProperty() {
return fileName;
}
Returns the value of the File property.
This method may be overridden by derived classes.
/**
* Returns the value of the <b>File</b> property.
*
* <p>
* This method may be overridden by derived classes.
*
*/
public String getFile() {
return fileName;
}
If the value of File is not null
, then openFile
is called with the values of File and
Append properties.
/**
* If the value of <b>File</b> is not <code>null</code>, then
* {@link #openFile} is called with the values of <b>File</b> and
* <b>Append</b> properties.
*/
public void start() {
int errors = 0;
if (getFile() != null) {
addInfo("File property is set to [" + fileName + "]");
if (prudent) {
if (!isAppend()) {
setAppend(true);
addWarn("Setting \"Append\" property to true on account of \"Prudent\" mode");
}
}
if (checkForFileCollisionInPreviousFileAppenders()) {
addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL);
errors++;
} else {
// file should be opened only if collision free
try {
openFile(getFile());
} catch (java.io.IOException e) {
errors++;
addError("openFile(" + fileName + "," + append + ") call failed.", e);
}
}
} else {
errors++;
addError("\"File\" property not set for appender named [" + name + "].");
}
if (errors == 0) {
super.start();
}
}
@Override
public void stop() {
super.stop();
Map<String, String> map = ContextUtil.getFilenameCollisionMap(context);
if (map == null || getName() == null)
return;
map.remove(getName());
}
protected boolean checkForFileCollisionInPreviousFileAppenders() {
boolean collisionsDetected = false;
if (fileName == null) {
return false;
}
@SuppressWarnings("unchecked")
Map<String, String> map = (Map<String, String>) context.getObject(CoreConstants.FA_FILENAME_COLLISION_MAP);
if (map == null) {
return collisionsDetected;
}
for (Entry<String, String> entry : map.entrySet()) {
if (fileName.equals(entry.getValue())) {
addErrorForCollision("File", entry.getValue(), entry.getKey());
collisionsDetected = true;
}
}
if (name != null) {
map.put(getName(), fileName);
}
return collisionsDetected;
}
protected void addErrorForCollision(String optionName, String optionValue, String appenderName) {
addError("'" + optionName + "' option has the same value \"" + optionValue + "\" as that given for appender [" + appenderName + "] defined earlier.");
}
Sets and opens the file where the log output will go. The specified
file must be writable.
If there was already an opened file, then the previous file is closed
first.
Do not use this method directly. To configure a FileAppender or one of
its subclasses, set its properties one by one and then call start().
Params: - file_name –
The path to the log file.
/**
* <p>
* Sets and <i>opens</i> the file where the log output will go. The specified
* file must be writable.
*
* <p>
* If there was already an opened file, then the previous file is closed
* first.
*
* <p>
* <b>Do not use this method directly. To configure a FileAppender or one of
* its subclasses, set its properties one by one and then call start().</b>
*
* @param file_name
* The path to the log file.
*/
public void openFile(String file_name) throws IOException {
lock.lock();
try {
File file = new File(file_name);
boolean result = FileUtil.createMissingParentDirectories(file);
if (!result) {
addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]");
}
ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append, bufferSize.getSize());
resilientFos.setContext(context);
setOutputStream(resilientFos);
} finally {
lock.unlock();
}
}
See Also: - setPrudent(boolean)
Returns: true if in prudent mode
/**
* @see #setPrudent(boolean)
*
* @return true if in prudent mode
*/
public boolean isPrudent() {
return prudent;
}
When prudent is set to true, file appenders from multiple JVMs can safely
write to the same file.
Params: - prudent –
/**
* When prudent is set to true, file appenders from multiple JVMs can safely
* write to the same file.
*
* @param prudent
*/
public void setPrudent(boolean prudent) {
this.prudent = prudent;
}
public void setAppend(boolean append) {
this.append = append;
}
public void setBufferSize(FileSize bufferSize) {
addInfo("Setting bufferSize to ["+bufferSize.toString()+"]");
this.bufferSize = bufferSize;
}
private void safeWrite(E event) throws IOException {
ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream();
FileChannel fileChannel = resilientFOS.getChannel();
if (fileChannel == null) {
return;
}
// Clear any current interrupt (see LOGBACK-875)
boolean interrupted = Thread.interrupted();
FileLock fileLock = null;
try {
fileLock = fileChannel.lock();
long position = fileChannel.position();
long size = fileChannel.size();
if (size != position) {
fileChannel.position(size);
}
super.writeOut(event);
} catch (IOException e) {
// Mainly to catch FileLockInterruptionExceptions (see LOGBACK-875)
resilientFOS.postIOFailure(e);
} finally {
if (fileLock != null && fileLock.isValid()) {
fileLock.release();
}
// Re-interrupt if we started in an interrupted state (see LOGBACK-875)
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
@Override
protected void writeOut(E event) throws IOException {
if (prudent) {
safeWrite(event);
} else {
super.writeOut(event);
}
}
}