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