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&uuml;lc&uuml;
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        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}