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;
023import java.util.regex.PatternSyntaxException;
024
025import ch.qos.logback.core.CoreConstants;
026import ch.qos.logback.core.FileAppender;
027import ch.qos.logback.core.rolling.helper.CompressionMode;
028import ch.qos.logback.core.rolling.helper.FileNamePattern;
029import ch.qos.logback.core.util.ContextUtil;
030
031/**
032 * <code>RollingFileAppender</code> extends {@link FileAppender} to back up the
033 * log files depending on {@link RollingPolicy} and {@link TriggeringPolicy}.
034 *
035 * <p>
036 * For more information about this appender, please refer to the online manual
037 * at http://logback.qos.ch/manual/appenders.html#RollingFileAppender
038 *
039 * @author Heinz Richter
040 * @author Ceki G&uuml;lc&uuml;
041 */
042public class RollingFileAppender<E> extends FileAppender<E> {
043    File currentlyActiveFile;
044    TriggeringPolicy<E> triggeringPolicy;
045    RollingPolicy rollingPolicy;
046
047    Lock triggeringPolicyLock = new ReentrantLock();
048
049    static private String RFA_NO_TP_URL = CODES_URL + "#rfa_no_tp";
050    static private String RFA_NO_RP_URL = CODES_URL + "#rfa_no_rp";
051    static private String COLLISION_URL = CODES_URL + "#rfa_collision";
052    static private String RFA_LATE_FILE_URL = CODES_URL + "#rfa_file_after";
053    static private String RFA_RESET_RP_OR_TP = CODES_URL + "#rfa_reset_rp_or_tp";
054
055    public void start() {
056        if (triggeringPolicy == null) {
057            addWarn("No TriggeringPolicy was set for the RollingFileAppender named " + getName());
058            addWarn(MORE_INFO_PREFIX + RFA_NO_TP_URL);
059            return;
060        }
061        if (!triggeringPolicy.isStarted()) {
062            addWarn("TriggeringPolicy has not started. RollingFileAppender will not start");
063            return;
064        }
065
066//        if (checkForCollisionsInPreviousRollingFileAppenders()) {
067//            addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
068//            addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL);
069//            return;
070//        }
071
072        // we don't want to void existing log files
073        if (!append) {
074            addWarn("Append mode is mandatory for RollingFileAppender. Defaulting to append=true.");
075            append = true;
076        }
077
078        if (rollingPolicy == null) {
079            addError("No RollingPolicy was set for the RollingFileAppender named " + getName());
080            addError(MORE_INFO_PREFIX + RFA_NO_RP_URL);
081            return;
082        }
083
084        // sanity check for http://jira.qos.ch/browse/LOGBACK-796
085        if (checkForFileAndPatternCollisions()) {
086            addError("File property collides with fileNamePattern. Aborting.");
087            addError(MORE_INFO_PREFIX + COLLISION_URL);
088            return;
089        }
090
091        if (isPrudent()) {
092            if (rawFileProperty() != null) {
093                addWarn("Setting \"File\" property to null on account of prudent mode");
094                setFile(null);
095            }
096            if (rollingPolicy.getCompressionMode() != CompressionMode.NONE) {
097                addError("Compression is not supported in prudent mode. Aborting");
098                return;
099            }
100        }
101
102        addInfo("Active log file name: " + getFile());
103        currentlyActiveFile = new File(getFile());
104        initializeLengthCounter();
105        super.start();
106    }
107
108
109
110    private boolean checkForFileAndPatternCollisions() {
111        if (triggeringPolicy instanceof RollingPolicyBase) {
112            final RollingPolicyBase base = (RollingPolicyBase) triggeringPolicy;
113            final FileNamePattern fileNamePattern = base.fileNamePattern;
114            // no use checking if either fileName or fileNamePattern are null
115            if (fileNamePattern != null && fileName != null) {
116                String regex = fileNamePattern.toRegex();
117                try {
118                    return fileName.matches(regex);
119                } catch (PatternSyntaxException e) {
120                    // the check is a safeguard and should not prevent the appender from starting
121                    addWarn("Could not check for collisions between file property [" + fileName
122                            + "] and fileNamePattern [" + fileNamePattern + "] using regex [" + regex + "]", e);
123                    return false;
124                }
125            }
126        }
127        return false;
128    }
129
130    private void initializeLengthCounter() {
131        if(getLengthCounter() != null && currentlyActiveFile.exists()) {
132            long currentFileLength = currentlyActiveFile.length();
133            addInfo("Setting currentFileLength to "+currentFileLength+ " for "+currentlyActiveFile);
134            incrementByteCount(currentFileLength);
135        }
136    }
137
138    @Override
139    public void stop() {
140        if (!isStarted()) {
141            return;
142        }
143        super.stop();
144
145        if (rollingPolicy != null)
146            rollingPolicy.stop();
147        if (triggeringPolicy != null)
148            triggeringPolicy.stop();
149    }
150
151    @Override
152    public void setFile(String file) {
153        // http://jira.qos.ch/browse/LBCORE-94
154        // allow setting the file name to null if mandated by prudent mode
155        if (file != null && ((triggeringPolicy != null) || (rollingPolicy != null))) {
156            addError("File property must be set before any triggeringPolicy or rollingPolicy properties");
157            addError(MORE_INFO_PREFIX + RFA_LATE_FILE_URL);
158        }
159        super.setFile(file);
160    }
161
162    @Override
163    public String getFile() {
164        return rollingPolicy.getActiveFileName();
165    }
166
167    /**
168     * Implemented by delegating most of the rollover work to a rolling policy.
169     */
170    public void rollover() {
171        streamWriteLock.lock();
172        try {
173            // Note: This method needs to be synchronized because it needs exclusive
174            // access while it closes and then re-opens the target file.
175            //
176            // make sure to close the hereto active log file! Renaming under windows
177            // does not work for open files.
178            this.closeOutputStream();
179            attemptRollover();
180            attemptOpenFile();
181        } finally {
182            streamWriteLock.unlock();
183        }
184    }
185
186    private void attemptOpenFile() {
187        try {
188            // update the currentlyActiveFile LOGBACK-64
189            currentlyActiveFile = new File(rollingPolicy.getActiveFileName());
190
191            // This will also close the file. This is OK since multiple close operations are
192            // safe.
193            this.openFile(rollingPolicy.getActiveFileName());
194        } catch (IOException e) {
195            addError("setFile(" + fileName + ", false) call failed.", e);
196        }
197    }
198
199    private void attemptRollover() {
200        try {
201            rollingPolicy.rollover();
202        } catch (RolloverFailure rf) {
203            addWarn("RolloverFailure occurred. Deferring roll-over.");
204            // we failed to roll-over, let us not truncate and risk data loss
205            this.append = true;
206        }
207    }
208
209    /**
210     * This method differentiates RollingFileAppender from its super class.
211     */
212    @Override
213    protected void subAppend(E event) {
214
215        // We need to synchronize on triggeringPolicy so that only one rollover
216        // occurs at a time. We should also ensure that the triggeringPolicy.isTriggeringEvent
217        // method can ensure that it updates itself properly when isTriggeringEvent returns true
218
219        // The roll-over check must precede actual writing. This is the
220        // only correct behavior for time driven triggers.
221
222        triggeringPolicyLock.lock();
223        try {
224            if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) {
225                rollover();
226            }
227        } finally {
228            triggeringPolicyLock.unlock();
229        }
230
231        super.subAppend(event);
232    }
233
234    public RollingPolicy getRollingPolicy() {
235        return rollingPolicy;
236    }
237
238    public TriggeringPolicy<E> getTriggeringPolicy() {
239        return triggeringPolicy;
240    }
241
242    /**
243     * Sets the rolling policy. In case the 'policy' argument also implements
244     * {@link TriggeringPolicy}, then the triggering policy for this appender is
245     * automatically set to be the policy argument.
246     *
247     * @param policy
248     */
249    @SuppressWarnings("unchecked")
250    public void setRollingPolicy(RollingPolicy policy) {
251        if (this.rollingPolicy instanceof TriggeringPolicy) {
252            String className = rollingPolicy.getClass().getSimpleName();
253            addWarn("A rolling policy of type " + className + " was already set.");
254            addWarn("Note that " + className + " doubles as a TriggeringPolicy");
255            addWarn("See also " + RFA_RESET_RP_OR_TP);
256        }
257        this.rollingPolicy = policy;
258        if (this.rollingPolicy instanceof TriggeringPolicy) {
259            this.triggeringPolicy = (TriggeringPolicy<E>) policy;
260        }
261
262    }
263
264    public void setTriggeringPolicy(TriggeringPolicy<E> policy) {
265        if (triggeringPolicy instanceof RollingPolicy) {
266            String className = triggeringPolicy.getClass().getSimpleName();
267            addWarn("A triggering policy of type " + className + " was already set.");
268            addWarn("Note that " + className + " doubles as a RollingPolicy");
269            addWarn("See also " + RFA_RESET_RP_OR_TP);
270        }
271        triggeringPolicy = policy;
272        if (policy instanceof RollingPolicy) {
273            rollingPolicy = (RollingPolicy) policy;
274        }
275    }
276
277    @Override
278    protected void updateByteCount(byte[] byteArray) {
279        if(byteArray == null)
280            return;
281        incrementByteCount(byteArray.length);
282    }
283
284    void incrementByteCount(long increment) {
285        LengthCounter lengthCounter = getLengthCounter();
286        if (lengthCounter == null)
287            return;
288
289        if (increment > 0) {
290            lengthCounter.add(increment);
291        }
292    }
293
294    private LengthCounter getLengthCounter() {
295        return triggeringPolicy.getLengthCounter();
296    }
297
298}