001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights 003 * reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License 006 * v1.0 as published by the Eclipse Foundation 007 * 008 * or (per the licensee's choosing) 009 * 010 * under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. 011 */ 012package ch.qos.logback.core.rolling; 013 014import static ch.qos.logback.core.CoreConstants.CODES_URL; 015import static ch.qos.logback.core.CoreConstants.MORE_INFO_PREFIX; 016 017import java.io.File; 018import java.io.IOException; 019import java.util.Map; 020import java.util.Map.Entry; 021import java.util.concurrent.locks.Lock; 022import java.util.concurrent.locks.ReentrantLock; 023 024import ch.qos.logback.core.CoreConstants; 025import ch.qos.logback.core.FileAppender; 026import ch.qos.logback.core.rolling.helper.CompressionMode; 027import ch.qos.logback.core.rolling.helper.FileNamePattern; 028import ch.qos.logback.core.util.ContextUtil; 029 030/** 031 * <code>RollingFileAppender</code> extends {@link FileAppender} to back up the 032 * log files depending on {@link RollingPolicy} and {@link TriggeringPolicy}. 033 * 034 * <p> 035 * For more information about this appender, please refer to the online manual 036 * at http://logback.qos.ch/manual/appenders.html#RollingFileAppender 037 * 038 * @author Heinz Richter 039 * @author Ceki Gülcü 040 */ 041public class RollingFileAppender<E> extends FileAppender<E> { 042 File currentlyActiveFile; 043 TriggeringPolicy<E> triggeringPolicy; 044 RollingPolicy rollingPolicy; 045 046 Lock triggeringPolicyLock = new ReentrantLock(); 047 048 static private String RFA_NO_TP_URL = CODES_URL + "#rfa_no_tp"; 049 static private String RFA_NO_RP_URL = CODES_URL + "#rfa_no_rp"; 050 static private String COLLISION_URL = CODES_URL + "#rfa_collision"; 051 static private String RFA_LATE_FILE_URL = CODES_URL + "#rfa_file_after"; 052 static private String RFA_RESET_RP_OR_TP = CODES_URL + "#rfa_reset_rp_or_tp"; 053 054 public void start() { 055 if (triggeringPolicy == null) { 056 addWarn("No TriggeringPolicy was set for the RollingFileAppender named " + getName()); 057 addWarn(MORE_INFO_PREFIX + RFA_NO_TP_URL); 058 return; 059 } 060 if (!triggeringPolicy.isStarted()) { 061 addWarn("TriggeringPolicy has not started. RollingFileAppender will not start"); 062 return; 063 } 064 065 if (checkForCollisionsInPreviousRollingFileAppenders()) { 066 addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting."); 067 addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL); 068 return; 069 } 070 071 // we don't want to void existing log files 072 if (!append) { 073 addWarn("Append mode is mandatory for RollingFileAppender. Defaulting to append=true."); 074 append = true; 075 } 076 077 if (rollingPolicy == null) { 078 addError("No RollingPolicy was set for the RollingFileAppender named " + getName()); 079 addError(MORE_INFO_PREFIX + RFA_NO_RP_URL); 080 return; 081 } 082 083 // sanity check for http://jira.qos.ch/browse/LOGBACK-796 084 if (checkForFileAndPatternCollisions()) { 085 addError("File property collides with fileNamePattern. Aborting."); 086 addError(MORE_INFO_PREFIX + COLLISION_URL); 087 return; 088 } 089 090 if (isPrudent()) { 091 if (rawFileProperty() != null) { 092 addWarn("Setting \"File\" property to null on account of prudent mode"); 093 setFile(null); 094 } 095 if (rollingPolicy.getCompressionMode() != CompressionMode.NONE) { 096 addError("Compression is not supported in prudent mode. Aborting"); 097 return; 098 } 099 } 100 101 addInfo("Active log file name: " + getFile()); 102 currentlyActiveFile = new File(getFile()); 103 initializeLengthCounter(); 104 super.start(); 105 } 106 107 108 109 private boolean checkForFileAndPatternCollisions() { 110 if (triggeringPolicy instanceof RollingPolicyBase) { 111 final RollingPolicyBase base = (RollingPolicyBase) triggeringPolicy; 112 final FileNamePattern fileNamePattern = base.fileNamePattern; 113 // no use checking if either fileName or fileNamePattern are null 114 if (fileNamePattern != null && fileName != null) { 115 String regex = fileNamePattern.toRegex(); 116 return fileName.matches(regex); 117 } 118 } 119 return false; 120 } 121 122 private boolean checkForCollisionsInPreviousRollingFileAppenders() { 123 boolean collisionResult = false; 124 if (triggeringPolicy instanceof RollingPolicyBase) { 125 final RollingPolicyBase base = (RollingPolicyBase) triggeringPolicy; 126 final FileNamePattern fileNamePattern = base.fileNamePattern; 127 boolean collisionsDetected = innerCheckForFileNamePatternCollisionInPreviousRFA(fileNamePattern); 128 if (collisionsDetected) 129 collisionResult = true; 130 } 131 return collisionResult; 132 } 133 134 private boolean innerCheckForFileNamePatternCollisionInPreviousRFA(FileNamePattern fileNamePattern) { 135 boolean collisionsDetected = false; 136 @SuppressWarnings("unchecked") Map<String, FileNamePattern> map = (Map<String, FileNamePattern>) context.getObject( 137 CoreConstants.RFA_FILENAME_PATTERN_COLLISION_MAP); 138 if (map == null) { 139 return collisionsDetected; 140 } 141 for (Entry<String, FileNamePattern> entry : map.entrySet()) { 142 if (fileNamePattern.equals(entry.getValue())) { 143 addErrorForCollision("FileNamePattern", entry.getValue().toString(), entry.getKey()); 144 collisionsDetected = true; 145 } 146 } 147 if (name != null) { 148 map.put(name, fileNamePattern); 149 } 150 return collisionsDetected; 151 } 152 153 private void initializeLengthCounter() { 154 if(getLengthCounter() != null && currentlyActiveFile.exists()) { 155 long currentFileLength = currentlyActiveFile.length(); 156 addInfo("Setting currentFileLength to "+currentFileLength+ " for "+currentlyActiveFile); 157 incrementByteCount(currentFileLength); 158 } 159 } 160 161 @Override 162 public void stop() { 163 if (!isStarted()) { 164 return; 165 } 166 super.stop(); 167 168 if (rollingPolicy != null) 169 rollingPolicy.stop(); 170 if (triggeringPolicy != null) 171 triggeringPolicy.stop(); 172 173 Map<String, FileNamePattern> map = ContextUtil.getFilenamePatternCollisionMap(context); 174 if (map != null && getName() != null) 175 map.remove(getName()); 176 177 } 178 179 @Override 180 public void setFile(String file) { 181 // http://jira.qos.ch/browse/LBCORE-94 182 // allow setting the file name to null if mandated by prudent mode 183 if (file != null && ((triggeringPolicy != null) || (rollingPolicy != null))) { 184 addError("File property must be set before any triggeringPolicy or rollingPolicy properties"); 185 addError(MORE_INFO_PREFIX + RFA_LATE_FILE_URL); 186 } 187 super.setFile(file); 188 } 189 190 @Override 191 public String getFile() { 192 return rollingPolicy.getActiveFileName(); 193 } 194 195 /** 196 * Implemented by delegating most of the rollover work to a rolling policy. 197 */ 198 public void rollover() { 199 streamWriteLock.lock(); 200 try { 201 // Note: This method needs to be synchronized because it needs exclusive 202 // access while it closes and then re-opens the target file. 203 // 204 // make sure to close the hereto active log file! Renaming under windows 205 // does not work for open files. 206 this.closeOutputStream(); 207 attemptRollover(); 208 attemptOpenFile(); 209 } finally { 210 streamWriteLock.unlock(); 211 } 212 } 213 214 private void attemptOpenFile() { 215 try { 216 // update the currentlyActiveFile LOGBACK-64 217 currentlyActiveFile = new File(rollingPolicy.getActiveFileName()); 218 219 // This will also close the file. This is OK since multiple close operations are 220 // safe. 221 this.openFile(rollingPolicy.getActiveFileName()); 222 } catch (IOException e) { 223 addError("setFile(" + fileName + ", false) call failed.", e); 224 } 225 } 226 227 private void attemptRollover() { 228 try { 229 rollingPolicy.rollover(); 230 } catch (RolloverFailure rf) { 231 addWarn("RolloverFailure occurred. Deferring roll-over."); 232 // we failed to roll-over, let us not truncate and risk data loss 233 this.append = true; 234 } 235 } 236 237 /** 238 * This method differentiates RollingFileAppender from its super class. 239 */ 240 @Override 241 protected void subAppend(E event) { 242 243 // We need to synchronize on triggeringPolicy so that only one rollover 244 // occurs at a time. We should also ensure that the triggeringPolicy.isTriggeringEvent 245 // method can ensure that it updates itself properly when isTriggeringEvent returns true 246 247 // The roll-over check must precede actual writing. This is the 248 // only correct behavior for time driven triggers. 249 250 triggeringPolicyLock.lock(); 251 try { 252 if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) { 253 rollover(); 254 } 255 } finally { 256 triggeringPolicyLock.unlock(); 257 } 258 259 super.subAppend(event); 260 } 261 262 public RollingPolicy getRollingPolicy() { 263 return rollingPolicy; 264 } 265 266 public TriggeringPolicy<E> getTriggeringPolicy() { 267 return triggeringPolicy; 268 } 269 270 /** 271 * Sets the rolling policy. In case the 'policy' argument also implements 272 * {@link TriggeringPolicy}, then the triggering policy for this appender is 273 * automatically set to be the policy argument. 274 * 275 * @param policy 276 */ 277 @SuppressWarnings("unchecked") 278 public void setRollingPolicy(RollingPolicy policy) { 279 if (this.rollingPolicy instanceof TriggeringPolicy) { 280 String className = rollingPolicy.getClass().getSimpleName(); 281 addWarn("A rolling policy of type " + className + " was already set."); 282 addWarn("Note that " + className + " doubles as a TriggeringPolicy"); 283 addWarn("See also " + RFA_RESET_RP_OR_TP); 284 } 285 this.rollingPolicy = policy; 286 if (this.rollingPolicy instanceof TriggeringPolicy) { 287 this.triggeringPolicy = (TriggeringPolicy<E>) policy; 288 } 289 290 } 291 292 public void setTriggeringPolicy(TriggeringPolicy<E> policy) { 293 if (triggeringPolicy instanceof RollingPolicy) { 294 String className = triggeringPolicy.getClass().getSimpleName(); 295 addWarn("A triggering policy of type " + className + " was already set."); 296 addWarn("Note that " + className + " doubles as a RollingPolicy"); 297 addWarn("See also " + RFA_RESET_RP_OR_TP); 298 } 299 triggeringPolicy = policy; 300 if (policy instanceof RollingPolicy) { 301 rollingPolicy = (RollingPolicy) policy; 302 } 303 } 304 305 @Override 306 protected void updateByteCount(byte[] byteArray) { 307 if(byteArray == null) 308 return; 309 incrementByteCount(byteArray.length); 310 } 311 312 void incrementByteCount(long increment) { 313 LengthCounter lengthCounter = getLengthCounter(); 314 if (lengthCounter == null) 315 return; 316 317 if (increment > 0) { 318 lengthCounter.add(increment); 319 } 320 } 321 322 private LengthCounter getLengthCounter() { 323 return triggeringPolicy.getLengthCounter(); 324 } 325 326}