001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, 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 v2.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;
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.nio.channels.FileChannel;
022import java.nio.channels.FileLock;
023import java.util.Map;
024import java.util.Map.Entry;
025
026import ch.qos.logback.core.recovery.ResilientFileOutputStream;
027import ch.qos.logback.core.util.ContextUtil;
028import ch.qos.logback.core.util.FileSize;
029import ch.qos.logback.core.util.FileUtil;
030
031/**
032 * FileAppender appends log events to a file.
033 *
034 * For more information about this appender, please refer to the online manual
035 * at http://logback.qos.ch/manual/appenders.html#FileAppender
036 *
037 * @author Ceki Gülcü
038 */
039public class FileAppender<E> extends OutputStreamAppender<E> {
040
041    public static final long DEFAULT_BUFFER_SIZE = 8192;
042
043    static protected String COLLISION_WITH_EARLIER_APPENDER_URL = CODES_URL + "#earlier_fa_collision";
044
045    /**
046     * Append to or truncate the file? The default value for this variable is
047     * <code>true</code>, meaning that by default a <code>FileAppender</code> will
048     * append to an existing file and not truncate it.
049     */
050    protected boolean append = true;
051
052    /**
053     * The name of the active log file.
054     */
055    protected String fileName = null;
056
057    private boolean prudent = false;
058
059    private FileSize bufferSize = new FileSize(DEFAULT_BUFFER_SIZE);
060
061    /**
062     * The <b>File</b> property takes a string value which should be the name of the
063     * file to append to.
064     */
065    public void setFile(String file) {
066        if (file == null) {
067            fileName = file;
068        } else {
069            // Trim spaces from both ends. The users probably does not want
070            // trailing spaces in file names.
071            fileName = file.trim();
072        }
073    }
074
075    /**
076     * Returns the value of the <b>Append</b> property.
077     */
078    public boolean isAppend() {
079        return append;
080    }
081
082    /**
083     * This method is used by derived classes to obtain the raw file property.
084     * Regular users should not be calling this method.
085     *
086     * @return the value of the file property
087     */
088    final public String rawFileProperty() {
089        return fileName;
090    }
091
092    /**
093     * Returns the value of the <b>File</b> property.
094     *
095     * <p>
096     * This method may be overridden by derived classes.
097     *
098     */
099    public String getFile() {
100        return fileName;
101    }
102
103    /**
104     * If the value of <b>File</b> is not <code>null</code>, then {@link #openFile}
105     * is called with the values of <b>File</b> and <b>Append</b> properties.
106     */
107    public void start() {
108        int errors = 0;
109        if (getFile() != null) {
110            addInfo("File property is set to [" + fileName + "]");
111
112            if (prudent) {
113                if (!isAppend()) {
114                    setAppend(true);
115                    addWarn("Setting \"Append\" property to true on account of \"Prudent\" mode");
116                }
117            }
118
119            try {
120                openFile(getFile());
121            } catch (java.io.IOException e) {
122                errors++;
123                addError("openFile(" + fileName + "," + append + ") call failed.", e);
124            }
125        } else {
126            errors++;
127            addError("\"File\" property not set for appender named [" + name + "].");
128        }
129        if (errors == 0) {
130            super.start();
131        }
132    }
133
134    @Override
135    public void stop() {
136        if (!isStarted())
137            return;
138
139        super.stop();
140    }
141
142    /**
143     * <p>
144     * Sets and <i>opens</i> the file where the log output will go. The specified
145     * file must be writable.
146     *
147     * <p>
148     * If there was already an opened file, then the previous file is closed first.
149     *
150     * <p>
151     * <b>Do not use this method directly. To configure a FileAppender or one of its
152     * subclasses, set its properties one by one and then call start().</b>
153     *
154     * @param file_name The path to the log file.
155     */
156    public void openFile(String file_name) throws IOException {
157        streamWriteLock.lock();
158        try {
159            File file = new File(file_name);
160            boolean result = FileUtil.createMissingParentDirectories(file);
161            if (!result) {
162                addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]");
163            }
164
165            ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append, bufferSize.getSize());
166            resilientFos.setContext(context);
167            setOutputStream(resilientFos);
168        } finally {
169            streamWriteLock.unlock();
170        }
171    }
172
173    /**
174     * @see #setPrudent(boolean)
175     *
176     * @return true if in prudent mode
177     */
178    public boolean isPrudent() {
179        return prudent;
180    }
181
182    /**
183     * When prudent is set to true, file appenders from multiple JVMs can safely
184     * write to the same file.
185     *
186     * @param prudent
187     */
188    public void setPrudent(boolean prudent) {
189        this.prudent = prudent;
190    }
191
192    public void setAppend(boolean append) {
193        this.append = append;
194    }
195
196    public void setBufferSize(FileSize bufferSize) {
197        addInfo("Setting bufferSize to [" + bufferSize.toString() + "]");
198        this.bufferSize = bufferSize;
199    }
200
201    @Override
202    protected void writeOut(E event) throws IOException {
203        if (prudent) {
204            safeWriteOut(event);
205        } else {
206            super.writeOut(event);
207        }
208    }
209
210    private void safeWriteOut(E event) {
211        byte[] byteArray = this.encoder.encode(event);
212        if (byteArray == null || byteArray.length == 0)
213            return;
214
215        streamWriteLock.lock();
216        try {
217            safeWriteBytes(byteArray);
218        } finally {
219            streamWriteLock.unlock();
220        }
221    }
222
223    private void safeWriteBytes(byte[] byteArray) {
224        ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream();
225        FileChannel fileChannel = resilientFOS.getChannel();
226        if (fileChannel == null) {
227            return;
228        }
229
230        // Clear any current interrupt (see LOGBACK-875)
231        boolean interrupted = Thread.interrupted();
232
233        FileLock fileLock = null;
234        try {
235            fileLock = fileChannel.lock();
236            long position = fileChannel.position();
237            long size = fileChannel.size();
238            if (size != position) {
239                fileChannel.position(size);
240            }
241            writeByteArrayToOutputStreamWithPossibleFlush(byteArray);
242        } catch (IOException e) {
243            // Mainly to catch FileLockInterruptionExceptions (see LOGBACK-875)
244            resilientFOS.postIOFailure(e);
245        } finally {
246            releaseFileLock(fileLock);
247
248            // Re-interrupt if we started in an interrupted state (see LOGBACK-875)
249            if (interrupted) {
250                Thread.currentThread().interrupt();
251            }
252        }
253    }
254
255    private void releaseFileLock(FileLock fileLock) {
256        if (fileLock != null && fileLock.isValid()) {
257            try {
258                fileLock.release();
259            } catch (IOException e) {
260                addError("failed to release lock", e);
261            }
262        }
263    }
264}