View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.rolling;
15  
16  import static ch.qos.logback.core.CoreConstants.CODES_URL;
17  import static ch.qos.logback.core.CoreConstants.MORE_INFO_PREFIX;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.util.Map;
22  import java.util.Map.Entry;
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      static private String RFA_NO_TP_URL = CODES_URL + "#rfa_no_tp";
47      static private String RFA_NO_RP_URL = CODES_URL + "#rfa_no_rp";
48      static private String COLLISION_URL = CODES_URL + "#rfa_collision";
49      static private String RFA_LATE_FILE_URL = CODES_URL + "#rfa_file_after";
50  
51      public void start() {
52          if (triggeringPolicy == null) {
53              addWarn("No TriggeringPolicy was set for the RollingFileAppender named " + getName());
54              addWarn(MORE_INFO_PREFIX + RFA_NO_TP_URL);
55              return;
56          }
57          if (!triggeringPolicy.isStarted()) {
58              addWarn("TriggeringPolicy has not started. RollingFileAppender will not start");
59              return;
60          }
61  
62          if (checkForCollisionsInPreviousRollingFileAppenders()) {
63              addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
64              addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL);
65              return;
66          }
67  
68          // we don't want to void existing log files
69          if (!append) {
70              addWarn("Append mode is mandatory for RollingFileAppender. Defaulting to append=true.");
71              append = true;
72          }
73  
74          if (rollingPolicy == null) {
75              addError("No RollingPolicy was set for the RollingFileAppender named " + getName());
76              addError(MORE_INFO_PREFIX + RFA_NO_RP_URL);
77              return;
78          }
79  
80          // sanity check for http://jira.qos.ch/browse/LOGBACK-796
81          if (checkForFileAndPatternCollisions()) {
82              addError("File property collides with fileNamePattern. Aborting.");
83              addError(MORE_INFO_PREFIX + COLLISION_URL);
84              return;
85          }
86  
87          if (isPrudent()) {
88              if (rawFileProperty() != null) {
89                  addWarn("Setting \"File\" property to null on account of prudent mode");
90                  setFile(null);
91              }
92              if (rollingPolicy.getCompressionMode() != CompressionMode.NONE) {
93                  addError("Compression is not supported in prudent mode. Aborting");
94                  return;
95              }
96          }
97  
98          currentlyActiveFile = new File(getFile());
99          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 }