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