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