View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
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    *
9    *   or (per the licensee's choosing)
10   *
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ülcü
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             if (checkForFileCollisionInPreviousFileAppenders()) {
120                 addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
121                 addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL);
122                 errors++;
123             } else {
124                 // file should be opened only if collision free
125                 try {
126                     openFile(getFile());
127                 } catch (java.io.IOException e) {
128                     errors++;
129                     addError("openFile(" + fileName + "," + append + ") call failed.", e);
130                 }
131             }
132         } else {
133             errors++;
134             addError("\"File\" property not set for appender named [" + name + "].");
135         }
136         if (errors == 0) {
137             super.start();
138         }
139     }
140 
141     @Override
142     public void stop() {
143         if(!isStarted())
144             return;
145 
146         super.stop();
147 
148         Map<String, String> map = ContextUtil.getFilenameCollisionMap(context);
149         if (map == null || getName() == null)
150             return;
151 
152         map.remove(getName());
153     }
154 
155     protected boolean checkForFileCollisionInPreviousFileAppenders() {
156         boolean collisionsDetected = false;
157         if (fileName == null) {
158             return false;
159         }
160         @SuppressWarnings("unchecked")
161         Map<String, String> previousFilesMap = (Map<String, String>) context
162                 .getObject(CoreConstants.FA_FILENAME_COLLISION_MAP);
163         if (previousFilesMap == null) {
164             return collisionsDetected;
165         }
166         for (Entry<String, String> entry : previousFilesMap.entrySet()) {
167             if (fileName.equals(entry.getValue())) {
168                 addErrorForCollision("File", entry.getValue(), entry.getKey());
169                 collisionsDetected = true;
170             }
171         }
172         if (name != null) {
173             previousFilesMap.put(getName(), fileName);
174         }
175         return collisionsDetected;
176     }
177 
178     protected void addErrorForCollision(String optionName, String optionValue, String appenderName) {
179         addError("'" + optionName + "' option has the same value \"" + optionValue + "\" as that given for appender ["
180                 + appenderName + "] defined earlier.");
181     }
182 
183     /**
184      * <p>
185      * Sets and <i>opens</i> the file where the log output will go. The specified
186      * file must be writable.
187      * 
188      * <p>
189      * If there was already an opened file, then the previous file is closed first.
190      * 
191      * <p>
192      * <b>Do not use this method directly. To configure a FileAppender or one of its
193      * subclasses, set its properties one by one and then call start().</b>
194      * 
195      * @param file_name The path to the log file.
196      */
197     public void openFile(String file_name) throws IOException {
198         streamWriteLock.lock();
199         try {
200             File file = new File(file_name);
201             boolean result = FileUtil.createMissingParentDirectories(file);
202             if (!result) {
203                 addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]");
204             }
205 
206             ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append, bufferSize.getSize());
207             resilientFos.setContext(context);
208             setOutputStream(resilientFos);
209         } finally {
210             streamWriteLock.unlock();
211         }
212     }
213 
214     /**
215      * @see #setPrudent(boolean)
216      * 
217      * @return true if in prudent mode
218      */
219     public boolean isPrudent() {
220         return prudent;
221     }
222 
223     /**
224      * When prudent is set to true, file appenders from multiple JVMs can safely
225      * write to the same file.
226      * 
227      * @param prudent
228      */
229     public void setPrudent(boolean prudent) {
230         this.prudent = prudent;
231     }
232 
233     public void setAppend(boolean append) {
234         this.append = append;
235     }
236 
237     public void setBufferSize(FileSize bufferSize) {
238         addInfo("Setting bufferSize to [" + bufferSize.toString() + "]");
239         this.bufferSize = bufferSize;
240     }
241 
242     @Override
243     protected void writeOut(E event) throws IOException {
244         if (prudent) {
245             safeWriteOut(event);
246         } else {
247             super.writeOut(event);
248         }
249     }
250 
251     private void safeWriteOut(E event) {
252         byte[] byteArray = this.encoder.encode(event);
253         if (byteArray == null || byteArray.length == 0)
254             return;
255 
256         streamWriteLock.lock();
257         try {
258            safeWriteBytes(byteArray);
259         } finally {
260             streamWriteLock.unlock();
261         }
262     }
263 
264     private void safeWriteBytes(byte[] byteArray) {
265         ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream();
266         FileChannel fileChannel = resilientFOS.getChannel();
267         if (fileChannel == null) {
268             return;
269         }
270 
271         // Clear any current interrupt (see LOGBACK-875)
272         boolean interrupted = Thread.interrupted();
273 
274         FileLock fileLock = null;
275         try {
276             fileLock = fileChannel.lock();
277             long position = fileChannel.position();
278             long size = fileChannel.size();
279             if (size != position) {
280                 fileChannel.position(size);
281             }
282             writeByteArrayToOutputStreamWithPossibleFlush(byteArray);
283         } catch (IOException e) {
284             // Mainly to catch FileLockInterruptionExceptions (see LOGBACK-875)
285             resilientFOS.postIOFailure(e);
286         } finally {
287             releaseFileLock(fileLock);
288 
289             // Re-interrupt if we started in an interrupted state (see LOGBACK-875)
290             if (interrupted) {
291                 Thread.currentThread().interrupt();
292             }
293         }
294     }
295 
296     private void releaseFileLock(FileLock fileLock) {
297         if (fileLock != null && fileLock.isValid()) {
298             try {
299                 fileLock.release();
300             } catch (IOException e) {
301                 addError("failed to release lock", e);
302             }
303         }
304     }
305 }