View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights
3    * reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License
6    * v1.0 as published by the Eclipse Foundation
7    *
8    * or (per the licensee's choosing)
9    *
10   * under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation.
11   */
12  package ch.qos.logback.core.rolling;
13  
14  import static ch.qos.logback.core.CoreConstants.CODES_URL;
15  import static ch.qos.logback.core.CoreConstants.MORE_INFO_PREFIX;
16  
17  import java.io.File;
18  import java.io.IOException;
19  import java.util.Map;
20  import java.util.Map.Entry;
21  import java.util.concurrent.locks.Lock;
22  import java.util.concurrent.locks.ReentrantLock;
23  
24  import ch.qos.logback.core.CoreConstants;
25  import ch.qos.logback.core.FileAppender;
26  import ch.qos.logback.core.rolling.helper.CompressionMode;
27  import ch.qos.logback.core.rolling.helper.FileNamePattern;
28  import ch.qos.logback.core.util.ContextUtil;
29  
30  /**
31   * <code>RollingFileAppender</code> extends {@link FileAppender} to back up the
32   * log files depending on {@link RollingPolicy} and {@link TriggeringPolicy}.
33   *
34   * <p>
35   * For more information about this appender, please refer to the online manual
36   * at http://logback.qos.ch/manual/appenders.html#RollingFileAppender
37   *
38   * @author Heinz Richter
39   * @author Ceki G&uuml;lc&uuml;
40   */
41  public class RollingFileAppender<E> extends FileAppender<E> {
42      File currentlyActiveFile;
43      TriggeringPolicy<E> triggeringPolicy;
44      RollingPolicy rollingPolicy;
45  
46      Lock triggeringPolicyLock = new ReentrantLock();
47  
48      static private String RFA_NO_TP_URL = CODES_URL + "#rfa_no_tp";
49      static private String RFA_NO_RP_URL = CODES_URL + "#rfa_no_rp";
50      static private String COLLISION_URL = CODES_URL + "#rfa_collision";
51      static private String RFA_LATE_FILE_URL = CODES_URL + "#rfa_file_after";
52      static private String RFA_RESET_RP_OR_TP = CODES_URL + "#rfa_reset_rp_or_tp";
53  
54      public void start() {
55          if (triggeringPolicy == null) {
56              addWarn("No TriggeringPolicy was set for the RollingFileAppender named " + getName());
57              addWarn(MORE_INFO_PREFIX + RFA_NO_TP_URL);
58              return;
59          }
60          if (!triggeringPolicy.isStarted()) {
61              addWarn("TriggeringPolicy has not started. RollingFileAppender will not start");
62              return;
63          }
64  
65          if (checkForCollisionsInPreviousRollingFileAppenders()) {
66              addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
67              addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL);
68              return;
69          }
70  
71          // we don't want to void existing log files
72          if (!append) {
73              addWarn("Append mode is mandatory for RollingFileAppender. Defaulting to append=true.");
74              append = true;
75          }
76  
77          if (rollingPolicy == null) {
78              addError("No RollingPolicy was set for the RollingFileAppender named " + getName());
79              addError(MORE_INFO_PREFIX + RFA_NO_RP_URL);
80              return;
81          }
82  
83          // sanity check for http://jira.qos.ch/browse/LOGBACK-796
84          if (checkForFileAndPatternCollisions()) {
85              addError("File property collides with fileNamePattern. Aborting.");
86              addError(MORE_INFO_PREFIX + COLLISION_URL);
87              return;
88          }
89  
90          if (isPrudent()) {
91              if (rawFileProperty() != null) {
92                  addWarn("Setting \"File\" property to null on account of prudent mode");
93                  setFile(null);
94              }
95              if (rollingPolicy.getCompressionMode() != CompressionMode.NONE) {
96                  addError("Compression is not supported in prudent mode. Aborting");
97                  return;
98              }
99          }
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 }