001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, 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 v1.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 if (checkForFileCollisionInPreviousFileAppenders()) { 120 addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting."); 121 addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL); 122 errors++; 123 } else { 124 // file should be opened only if collision free 125 try { 126 openFile(getFile()); 127 } catch (java.io.IOException e) { 128 errors++; 129 addError("openFile(" + fileName + "," + append + ") call failed.", e); 130 } 131 } 132 } else { 133 errors++; 134 addError("\"File\" property not set for appender named [" + name + "]."); 135 } 136 if (errors == 0) { 137 super.start(); 138 } 139 } 140 141 @Override 142 public void stop() { 143 if(!isStarted()) 144 return; 145 146 super.stop(); 147 148 Map<String, String> map = ContextUtil.getFilenameCollisionMap(context); 149 if (map == null || getName() == null) 150 return; 151 152 map.remove(getName()); 153 } 154 155 protected boolean checkForFileCollisionInPreviousFileAppenders() { 156 boolean collisionsDetected = false; 157 if (fileName == null) { 158 return false; 159 } 160 @SuppressWarnings("unchecked") 161 Map<String, String> previousFilesMap = (Map<String, String>) context 162 .getObject(CoreConstants.FA_FILENAME_COLLISION_MAP); 163 if (previousFilesMap == null) { 164 return collisionsDetected; 165 } 166 for (Entry<String, String> entry : previousFilesMap.entrySet()) { 167 if (fileName.equals(entry.getValue())) { 168 addErrorForCollision("File", entry.getValue(), entry.getKey()); 169 collisionsDetected = true; 170 } 171 } 172 if (name != null) { 173 previousFilesMap.put(getName(), fileName); 174 } 175 return collisionsDetected; 176 } 177 178 protected void addErrorForCollision(String optionName, String optionValue, String appenderName) { 179 addError("'" + optionName + "' option has the same value \"" + optionValue + "\" as that given for appender [" 180 + appenderName + "] defined earlier."); 181 } 182 183 /** 184 * <p> 185 * Sets and <i>opens</i> the file where the log output will go. The specified 186 * file must be writable. 187 * 188 * <p> 189 * If there was already an opened file, then the previous file is closed first. 190 * 191 * <p> 192 * <b>Do not use this method directly. To configure a FileAppender or one of its 193 * subclasses, set its properties one by one and then call start().</b> 194 * 195 * @param file_name The path to the log file. 196 */ 197 public void openFile(String file_name) throws IOException { 198 streamWriteLock.lock(); 199 try { 200 File file = new File(file_name); 201 boolean result = FileUtil.createMissingParentDirectories(file); 202 if (!result) { 203 addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]"); 204 } 205 206 ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append, bufferSize.getSize()); 207 resilientFos.setContext(context); 208 setOutputStream(resilientFos); 209 } finally { 210 streamWriteLock.unlock(); 211 } 212 } 213 214 /** 215 * @see #setPrudent(boolean) 216 * 217 * @return true if in prudent mode 218 */ 219 public boolean isPrudent() { 220 return prudent; 221 } 222 223 /** 224 * When prudent is set to true, file appenders from multiple JVMs can safely 225 * write to the same file. 226 * 227 * @param prudent 228 */ 229 public void setPrudent(boolean prudent) { 230 this.prudent = prudent; 231 } 232 233 public void setAppend(boolean append) { 234 this.append = append; 235 } 236 237 public void setBufferSize(FileSize bufferSize) { 238 addInfo("Setting bufferSize to [" + bufferSize.toString() + "]"); 239 this.bufferSize = bufferSize; 240 } 241 242 @Override 243 protected void writeOut(E event) throws IOException { 244 if (prudent) { 245 safeWriteOut(event); 246 } else { 247 super.writeOut(event); 248 } 249 } 250 251 private void safeWriteOut(E event) { 252 byte[] byteArray = this.encoder.encode(event); 253 if (byteArray == null || byteArray.length == 0) 254 return; 255 256 streamWriteLock.lock(); 257 try { 258 safeWriteBytes(byteArray); 259 } finally { 260 streamWriteLock.unlock(); 261 } 262 } 263 264 private void safeWriteBytes(byte[] byteArray) { 265 ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream(); 266 FileChannel fileChannel = resilientFOS.getChannel(); 267 if (fileChannel == null) { 268 return; 269 } 270 271 // Clear any current interrupt (see LOGBACK-875) 272 boolean interrupted = Thread.interrupted(); 273 274 FileLock fileLock = null; 275 try { 276 fileLock = fileChannel.lock(); 277 long position = fileChannel.position(); 278 long size = fileChannel.size(); 279 if (size != position) { 280 fileChannel.position(size); 281 } 282 writeByteArrayToOutputStreamWithPossibleFlush(byteArray); 283 } catch (IOException e) { 284 // Mainly to catch FileLockInterruptionExceptions (see LOGBACK-875) 285 resilientFOS.postIOFailure(e); 286 } finally { 287 releaseFileLock(fileLock); 288 289 // Re-interrupt if we started in an interrupted state (see LOGBACK-875) 290 if (interrupted) { 291 Thread.currentThread().interrupt(); 292 } 293 } 294 } 295 296 private void releaseFileLock(FileLock fileLock) { 297 if (fileLock != null && fileLock.isValid()) { 298 try { 299 fileLock.release(); 300 } catch (IOException e) { 301 addError("failed to release lock", e); 302 } 303 } 304 } 305}