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 currentlyActiveFile = new File(getFile()); 102 addInfo("Active log file name: " + getFile()); 103 super.start(); 104 } 105 106 private boolean checkForFileAndPatternCollisions() { 107 if (triggeringPolicy instanceof RollingPolicyBase) { 108 final RollingPolicyBase base = (RollingPolicyBase) triggeringPolicy; 109 final FileNamePattern fileNamePattern = base.fileNamePattern; 110 // no use checking if either fileName or fileNamePattern are null 111 if (fileNamePattern != null && fileName != null) { 112 String regex = fileNamePattern.toRegex(); 113 return fileName.matches(regex); 114 } 115 } 116 return false; 117 } 118 119 private boolean checkForCollisionsInPreviousRollingFileAppenders() { 120 boolean collisionResult = false; 121 if (triggeringPolicy instanceof RollingPolicyBase) { 122 final RollingPolicyBase base = (RollingPolicyBase) triggeringPolicy; 123 final FileNamePattern fileNamePattern = base.fileNamePattern; 124 boolean collisionsDetected = innerCheckForFileNamePatternCollisionInPreviousRFA(fileNamePattern); 125 if (collisionsDetected) 126 collisionResult = true; 127 } 128 return collisionResult; 129 } 130 131 private boolean innerCheckForFileNamePatternCollisionInPreviousRFA(FileNamePattern fileNamePattern) { 132 boolean collisionsDetected = false; 133 @SuppressWarnings("unchecked") Map<String, FileNamePattern> map = (Map<String, FileNamePattern>) context.getObject( 134 CoreConstants.RFA_FILENAME_PATTERN_COLLISION_MAP); 135 if (map == null) { 136 return collisionsDetected; 137 } 138 for (Entry<String, FileNamePattern> entry : map.entrySet()) { 139 if (fileNamePattern.equals(entry.getValue())) { 140 addErrorForCollision("FileNamePattern", entry.getValue().toString(), entry.getKey()); 141 collisionsDetected = true; 142 } 143 } 144 if (name != null) { 145 map.put(getName(), fileNamePattern); 146 } 147 return collisionsDetected; 148 } 149 150 @Override 151 public void stop() { 152 if (!isStarted()) { 153 return; 154 } 155 super.stop(); 156 157 if (rollingPolicy != null) 158 rollingPolicy.stop(); 159 if (triggeringPolicy != null) 160 triggeringPolicy.stop(); 161 162 Map<String, FileNamePattern> map = ContextUtil.getFilenamePatternCollisionMap(context); 163 if (map != null && getName() != null) 164 map.remove(getName()); 165 166 } 167 168 @Override 169 public void setFile(String file) { 170 // http://jira.qos.ch/browse/LBCORE-94 171 // allow setting the file name to null if mandated by prudent mode 172 if (file != null && ((triggeringPolicy != null) || (rollingPolicy != null))) { 173 addError("File property must be set before any triggeringPolicy or rollingPolicy properties"); 174 addError(MORE_INFO_PREFIX + RFA_LATE_FILE_URL); 175 } 176 super.setFile(file); 177 } 178 179 @Override 180 public String getFile() { 181 return rollingPolicy.getActiveFileName(); 182 } 183 184 /** 185 * Implemented by delegating most of the rollover work to a rolling policy. 186 */ 187 public void rollover() { 188 streamWriteLock.lock(); 189 try { 190 // Note: This method needs to be synchronized because it needs exclusive 191 // access while it closes and then re-opens the target file. 192 // 193 // make sure to close the hereto active log file! Renaming under windows 194 // does not work for open files. 195 this.closeOutputStream(); 196 attemptRollover(); 197 attemptOpenFile(); 198 } finally { 199 streamWriteLock.unlock(); 200 } 201 } 202 203 private void attemptOpenFile() { 204 try { 205 // update the currentlyActiveFile LOGBACK-64 206 currentlyActiveFile = new File(rollingPolicy.getActiveFileName()); 207 208 // This will also close the file. This is OK since multiple close operations are 209 // safe. 210 this.openFile(rollingPolicy.getActiveFileName()); 211 } catch (IOException e) { 212 addError("setFile(" + fileName + ", false) call failed.", e); 213 } 214 } 215 216 private void attemptRollover() { 217 try { 218 rollingPolicy.rollover(); 219 } catch (RolloverFailure rf) { 220 addWarn("RolloverFailure occurred. Deferring roll-over."); 221 // we failed to roll-over, let us not truncate and risk data loss 222 this.append = true; 223 } 224 } 225 226 /** 227 * This method differentiates RollingFileAppender from its super class. 228 */ 229 @Override 230 protected void subAppend(E event) { 231 232 // We need to synchronize on triggeringPolicy so that only one rollover 233 // occurs at a time. We should also ensure that the triggeringPolicy.isTriggeringEvent 234 // method can ensure that it updates itself properly when isTriggeringEvent returns true 235 236 // The roll-over check must precede actual writing. This is the 237 // only correct behavior for time driven triggers. 238 239 triggeringPolicyLock.lock(); 240 try { 241 if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) { 242 rollover(); 243 } 244 } finally { 245 triggeringPolicyLock.unlock(); 246 } 247 248 super.subAppend(event); 249 } 250 251 public RollingPolicy getRollingPolicy() { 252 return rollingPolicy; 253 } 254 255 public TriggeringPolicy<E> getTriggeringPolicy() { 256 return triggeringPolicy; 257 } 258 259 /** 260 * Sets the rolling policy. In case the 'policy' argument also implements 261 * {@link TriggeringPolicy}, then the triggering policy for this appender is 262 * automatically set to be the policy argument. 263 * 264 * @param policy 265 */ 266 @SuppressWarnings("unchecked") 267 public void setRollingPolicy(RollingPolicy policy) { 268 if (this.rollingPolicy instanceof TriggeringPolicy) { 269 String className = rollingPolicy.getClass().getSimpleName(); 270 addWarn("A rolling policy of type " + className + " was already set."); 271 addWarn("Note that " + className + " doubles as a TriggeringPolicy"); 272 addWarn("See also " + RFA_RESET_RP_OR_TP); 273 } 274 this.rollingPolicy = policy; 275 if (this.rollingPolicy instanceof TriggeringPolicy) { 276 this.triggeringPolicy = (TriggeringPolicy<E>) policy; 277 } 278 279 } 280 281 public void setTriggeringPolicy(TriggeringPolicy<E> policy) { 282 if (triggeringPolicy instanceof RollingPolicy) { 283 String className = triggeringPolicy.getClass().getSimpleName(); 284 addWarn("A triggering policy of type " + className + " was already set."); 285 addWarn("Note that " + className + " doubles as a RollingPolicy"); 286 addWarn("See also " + RFA_RESET_RP_OR_TP); 287 } 288 triggeringPolicy = policy; 289 if (policy instanceof RollingPolicy) { 290 rollingPolicy = (RollingPolicy) policy; 291 } 292 } 293 294 @Override 295 protected void updateByteCount(byte[] byteArray) { 296 297 LengthCounter lengthCounter = getLengthCounter(); 298 if (lengthCounter == null) 299 return; 300 301 if (byteArray != null && byteArray.length > 0) { 302 lengthCounter.add(byteArray.length); 303 } 304 } 305 306 private LengthCounter getLengthCounter() { 307 return triggeringPolicy.getLengthCounter(); 308 } 309 310}