1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    * <p>
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    * <p>
9    * or (per the licensee's choosing)
10   * <p>
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;
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.nio.channels.FileChannel;
22  import java.nio.channels.FileLock;
23  import java.util.Map;
24  import java.util.Map.Entry;
25  
26  import ch.qos.logback.core.recovery.ResilientFileOutputStream;
27  import ch.qos.logback.core.util.ContextUtil;
28  import ch.qos.logback.core.util.FileSize;
29  import ch.qos.logback.core.util.FileUtil;
30  
31  /**
32   * FileAppender appends log events to a file.
33   *
34   * For more information about this appender, please refer to the online manual
35   * at http://logback.qos.ch/manual/appenders.html#FileAppender
36   *
37   * @author Ceki G&uuml;lc&uuml;
38   */
39  public class FileAppender<E> extends OutputStreamAppender<E> {
40  
41      public static final long DEFAULT_BUFFER_SIZE = 8192;
42  
43      static protected String COLLISION_WITH_EARLIER_APPENDER_URL = CODES_URL + "#earlier_fa_collision";
44  
45      /**
46       * Append to or truncate the file? The default value for this variable is
47       * <code>true</code>, meaning that by default a <code>FileAppender</code> will
48       * append to an existing file and not truncate it.
49       */
50      protected boolean append = true;
51  
52      /**
53       * The name of the active log file.
54       */
55      protected String fileName = null;
56  
57      private boolean prudent = false;
58  
59      private FileSize bufferSize = new FileSize(DEFAULT_BUFFER_SIZE);
60  
61      /**
62       * The <b>File</b> property takes a string value which should be the name of the
63       * file to append to.
64       */
65      public void setFile(String file) {
66          if (file == null) {
67              fileName = file;
68          } else {
69              // Trim spaces from both ends. The users probably does not want
70              // trailing spaces in file names.
71              fileName = file.trim();
72          }
73      }
74  
75      /**
76       * Returns the value of the <b>Append</b> property.
77       */
78      public boolean isAppend() {
79          return append;
80      }
81  
82      /**
83       * This method is used by derived classes to obtain the raw file property.
84       * Regular users should not be calling this method.
85       *
86       * @return the value of the file property
87       */
88      final public String rawFileProperty() {
89          return fileName;
90      }
91  
92      /**
93       * Returns the value of the <b>File</b> property.
94       *
95       * <p>
96       * This method may be overridden by derived classes.
97       *
98       */
99      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 }