聊聊logback的FileAppender
序
本文主要研究一下logback的FileAppender
FileAppender
ch/qos/logback/core/FileAppender.java
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* <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.*/protected String fileName = null;private boolean prudent = false;private FileSize bufferSize = new FileSize(DEFAULT_BUFFER_SIZE);//......
}
FileAppender继承了OutputStreamAppender,它定义了append、prudent、bufferSize属性
start
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 freetry {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();}}
start方法要求fileName必须有值,在prudent模式下会强制开启append;另外start的时候会执行checkForFileCollisionInPreviousFileAppenders判断是否有冲突,没有冲突则执行openFile方法
checkForFileCollisionInPreviousFileAppenders
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;}
checkForFileCollisionInPreviousFileAppenders方法从上下文读取FA_FILENAME_COLLISION_MAP,判断有没有文件名重复的,有则返回true
openFile
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();}}
openFile方法加锁创建file,然后通过createMissingParentDirectories来创建不存在的父目录,最后创建根据file、append参数、bufferSize来创建ResilientFileOutputStream
stop
public void stop() {super.stop();Map<String, String> map = ContextUtil.getFilenameCollisionMap(context);if (map == null || getName() == null)return;map.remove(getName());}
stop方法会获取FA_FILENAME_COLLISION_MAP,移除当前文件名
writeOut
protected void writeOut(E event) throws IOException {if (prudent) {safeWrite(event);} else {super.writeOut(event);}}
FileAppender覆盖了OutputStreamAppender的writeOut方法,在prudent为true时执行safeWrite
safeWrite
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();}}}
safeWrite会通过fileChannel.lock()进行加锁,然后执行fileChannel.position(size),最后通过父类writeOut进行写入;对于IOException会执行resilientFOS.postIOFailure(e)
小结
logback的FileAppender继承了OutputStreamAppender,它定义了append、prudent、bufferSize属性,它使用的是ResilientFileOutputStream,其writeOut方法主要是新增了对prudent模式的支持,在prudent为true时采用的是safeWrite。