001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v2.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core; 015 016import static ch.qos.logback.core.CoreConstants.CODES_URL; 017import static ch.qos.logback.core.CoreConstants.MORE_INFO_PREFIX; 018 019import java.io.File; 020import java.io.IOException; 021import java.nio.channels.FileChannel; 022import java.nio.channels.FileLock; 023import java.util.Map; 024import java.util.Map.Entry; 025 026import ch.qos.logback.core.recovery.ResilientFileOutputStream; 027import ch.qos.logback.core.util.ContextUtil; 028import ch.qos.logback.core.util.FileSize; 029import ch.qos.logback.core.util.FileUtil; 030 031/** 032 * FileAppender appends log events to a file. 033 * 034 * For more information about this appender, please refer to the online manual 035 * at http://logback.qos.ch/manual/appenders.html#FileAppender 036 * 037 * @author Ceki Gülcü 038 */ 039public class FileAppender<E> extends OutputStreamAppender<E> { 040 041 public static final long DEFAULT_BUFFER_SIZE = 8192; 042 043 static protected String COLLISION_WITH_EARLIER_APPENDER_URL = CODES_URL + "#earlier_fa_collision"; 044 045 /** 046 * Append to or truncate the file? The default value for this variable is 047 * <code>true</code>, meaning that by default a <code>FileAppender</code> will 048 * append to an existing file and not truncate it. 049 */ 050 protected boolean append = true; 051 052 /** 053 * The name of the active log file. 054 */ 055 protected String fileName = null; 056 057 private boolean prudent = false; 058 059 private FileSize bufferSize = new FileSize(DEFAULT_BUFFER_SIZE); 060 061 /** 062 * The <b>File</b> property takes a string value which should be the name of the 063 * file to append to. 064 */ 065 public void setFile(String file) { 066 if (file == null) { 067 fileName = file; 068 } else { 069 // Trim spaces from both ends. The users probably does not want 070 // trailing spaces in file names. 071 fileName = file.trim(); 072 } 073 } 074 075 /** 076 * Returns the value of the <b>Append</b> property. 077 */ 078 public boolean isAppend() { 079 return append; 080 } 081 082 /** 083 * This method is used by derived classes to obtain the raw file property. 084 * Regular users should not be calling this method. 085 * 086 * @return the value of the file property 087 */ 088 final public String rawFileProperty() { 089 return fileName; 090 } 091 092 /** 093 * Returns the value of the <b>File</b> property. 094 * 095 * <p> 096 * This method may be overridden by derived classes. 097 * 098 */ 099 public String getFile() { 100 return fileName; 101 } 102 103 /** 104 * If the value of <b>File</b> is not <code>null</code>, then {@link #openFile} 105 * is called with the values of <b>File</b> and <b>Append</b> properties. 106 */ 107 public void start() { 108 int errors = 0; 109 if (getFile() != null) { 110 addInfo("File property is set to [" + fileName + "]"); 111 112 if (prudent) { 113 if (!isAppend()) { 114 setAppend(true); 115 addWarn("Setting \"Append\" property to true on account of \"Prudent\" mode"); 116 } 117 } 118 119 try { 120 openFile(getFile()); 121 } catch (java.io.IOException e) { 122 errors++; 123 addError("openFile(" + fileName + "," + append + ") call failed.", e); 124 } 125 } else { 126 errors++; 127 addError("\"File\" property not set for appender named [" + name + "]."); 128 } 129 if (errors == 0) { 130 super.start(); 131 } 132 } 133 134 @Override 135 public void stop() { 136 if (!isStarted()) 137 return; 138 139 super.stop(); 140 } 141 142 /** 143 * <p> 144 * Sets and <i>opens</i> the file where the log output will go. The specified 145 * file must be writable. 146 * 147 * <p> 148 * If there was already an opened file, then the previous file is closed first. 149 * 150 * <p> 151 * <b>Do not use this method directly. To configure a FileAppender or one of its 152 * subclasses, set its properties one by one and then call start().</b> 153 * 154 * @param file_name The path to the log file. 155 */ 156 public void openFile(String file_name) throws IOException { 157 streamWriteLock.lock(); 158 try { 159 File file = new File(file_name); 160 boolean result = FileUtil.createMissingParentDirectories(file); 161 if (!result) { 162 addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]"); 163 } 164 165 ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append, bufferSize.getSize()); 166 resilientFos.setContext(context); 167 setOutputStream(resilientFos); 168 } finally { 169 streamWriteLock.unlock(); 170 } 171 } 172 173 /** 174 * @see #setPrudent(boolean) 175 * 176 * @return true if in prudent mode 177 */ 178 public boolean isPrudent() { 179 return prudent; 180 } 181 182 /** 183 * When prudent is set to true, file appenders from multiple JVMs can safely 184 * write to the same file. 185 * 186 * @param prudent 187 */ 188 public void setPrudent(boolean prudent) { 189 this.prudent = prudent; 190 } 191 192 public void setAppend(boolean append) { 193 this.append = append; 194 } 195 196 public void setBufferSize(FileSize bufferSize) { 197 addInfo("Setting bufferSize to [" + bufferSize.toString() + "]"); 198 this.bufferSize = bufferSize; 199 } 200 201 @Override 202 protected void writeOut(E event) throws IOException { 203 if (prudent) { 204 safeWriteOut(event); 205 } else { 206 super.writeOut(event); 207 } 208 } 209 210 private void safeWriteOut(E event) { 211 byte[] byteArray = this.encoder.encode(event); 212 if (byteArray == null || byteArray.length == 0) 213 return; 214 215 streamWriteLock.lock(); 216 try { 217 safeWriteBytes(byteArray); 218 } finally { 219 streamWriteLock.unlock(); 220 } 221 } 222 223 private void safeWriteBytes(byte[] byteArray) { 224 ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream(); 225 FileChannel fileChannel = resilientFOS.getChannel(); 226 if (fileChannel == null) { 227 return; 228 } 229 230 // Clear any current interrupt (see LOGBACK-875) 231 boolean interrupted = Thread.interrupted(); 232 233 FileLock fileLock = null; 234 try { 235 fileLock = fileChannel.lock(); 236 long position = fileChannel.position(); 237 long size = fileChannel.size(); 238 if (size != position) { 239 fileChannel.position(size); 240 } 241 writeByteArrayToOutputStreamWithPossibleFlush(byteArray); 242 } catch (IOException e) { 243 // Mainly to catch FileLockInterruptionExceptions (see LOGBACK-875) 244 resilientFOS.postIOFailure(e); 245 } finally { 246 releaseFileLock(fileLock); 247 248 // Re-interrupt if we started in an interrupted state (see LOGBACK-875) 249 if (interrupted) { 250 Thread.currentThread().interrupt(); 251 } 252 } 253 } 254 255 private void releaseFileLock(FileLock fileLock) { 256 if (fileLock != null && fileLock.isValid()) { 257 try { 258 fileLock.release(); 259 } catch (IOException e) { 260 addError("failed to release lock", e); 261 } 262 } 263 } 264}