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