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