001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.rolling;
015
016import static ch.qos.logback.core.CoreConstants.CODES_URL;
017import static ch.qos.logback.core.CoreConstants.MORE_INFO_PREFIX;
018
019import java.io.File;
020import java.io.IOException;
021import java.util.Map;
022import java.util.Map.Entry;
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    static private String RFA_NO_TP_URL = CODES_URL + "#rfa_no_tp";
047    static private String RFA_NO_RP_URL = CODES_URL + "#rfa_no_rp";
048    static private String COLLISION_URL = CODES_URL + "#rfa_collision";
049    static private String RFA_LATE_FILE_URL = CODES_URL + "#rfa_file_after";
050
051    public void start() {
052        if (triggeringPolicy == null) {
053            addWarn("No TriggeringPolicy was set for the RollingFileAppender named " + getName());
054            addWarn(MORE_INFO_PREFIX + RFA_NO_TP_URL);
055            return;
056        }
057        if (!triggeringPolicy.isStarted()) {
058            addWarn("TriggeringPolicy has not started. RollingFileAppender will not start");
059            return;
060        }
061
062        if (checkForCollisionsInPreviousRollingFileAppenders()) {
063            addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
064            addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL);
065            return;
066        }
067
068        // we don't want to void existing log files
069        if (!append) {
070            addWarn("Append mode is mandatory for RollingFileAppender. Defaulting to append=true.");
071            append = true;
072        }
073
074        if (rollingPolicy == null) {
075            addError("No RollingPolicy was set for the RollingFileAppender named " + getName());
076            addError(MORE_INFO_PREFIX + RFA_NO_RP_URL);
077            return;
078        }
079
080        // sanity check for http://jira.qos.ch/browse/LOGBACK-796
081        if (checkForFileAndPatternCollisions()) {
082            addError("File property collides with fileNamePattern. Aborting.");
083            addError(MORE_INFO_PREFIX + COLLISION_URL);
084            return;
085        }
086
087        if (isPrudent()) {
088            if (rawFileProperty() != null) {
089                addWarn("Setting \"File\" property to null on account of prudent mode");
090                setFile(null);
091            }
092            if (rollingPolicy.getCompressionMode() != CompressionMode.NONE) {
093                addError("Compression is not supported in prudent mode. Aborting");
094                return;
095            }
096        }
097
098        currentlyActiveFile = new File(getFile());
099        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}