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         super.stop();
144 
145         Map<String, String> map = ContextUtil.getFilenameCollisionMap(context);
146         if (map == null || getName() == null)
147             return;
148 
149         map.remove(getName());
150     }
151 
152     protected boolean checkForFileCollisionInPreviousFileAppenders() {
153         boolean collisionsDetected = false;
154         if (fileName == null) {
155             return false;
156         }
157         @SuppressWarnings("unchecked")
158         Map<String, String> previousFilesMap = (Map<String, String>) context
159                 .getObject(CoreConstants.FA_FILENAME_COLLISION_MAP);
160         if (previousFilesMap == null) {
161             return collisionsDetected;
162         }
163         for (Entry<String, String> entry : previousFilesMap.entrySet()) {
164             if (fileName.equals(entry.getValue())) {
165                 addErrorForCollision("File", entry.getValue(), entry.getKey());
166                 collisionsDetected = true;
167             }
168         }
169         if (name != null) {
170             previousFilesMap.put(getName(), fileName);
171         }
172         return collisionsDetected;
173     }
174 
175     protected void addErrorForCollision(String optionName, String optionValue, String appenderName) {
176         addError("'" + optionName + "' option has the same value \"" + optionValue + "\" as that given for appender ["
177                 + appenderName + "] defined earlier.");
178     }
179 
180     /**
181      * <p>
182      * Sets and <i>opens</i> the file where the log output will go. The specified
183      * file must be writable.
184      * 
185      * <p>
186      * If there was already an opened file, then the previous file is closed first.
187      * 
188      * <p>
189      * <b>Do not use this method directly. To configure a FileAppender or one of its
190      * subclasses, set its properties one by one and then call start().</b>
191      * 
192      * @param file_name The path to the log file.
193      */
194     public void openFile(String file_name) throws IOException {
195         lock.lock();
196         try {
197             File file = new File(file_name);
198             boolean result = FileUtil.createMissingParentDirectories(file);
199             if (!result) {
200                 addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]");
201             }
202 
203             ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append, bufferSize.getSize());
204             resilientFos.setContext(context);
205             setOutputStream(resilientFos);
206         } finally {
207             lock.unlock();
208         }
209     }
210 
211     /**
212      * @see #setPrudent(boolean)
213      * 
214      * @return true if in prudent mode
215      */
216     public boolean isPrudent() {
217         return prudent;
218     }
219 
220     /**
221      * When prudent is set to true, file appenders from multiple JVMs can safely
222      * write to the same file.
223      * 
224      * @param prudent
225      */
226     public void setPrudent(boolean prudent) {
227         this.prudent = prudent;
228     }
229 
230     public void setAppend(boolean append) {
231         this.append = append;
232     }
233 
234     public void setBufferSize(FileSize bufferSize) {
235         addInfo("Setting bufferSize to [" + bufferSize.toString() + "]");
236         this.bufferSize = bufferSize;
237     }
238 
239     private void safeWrite(E event) throws IOException {
240         ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream();
241         FileChannel fileChannel = resilientFOS.getChannel();
242         if (fileChannel == null) {
243             return;
244         }
245 
246         // Clear any current interrupt (see LOGBACK-875)
247         boolean interrupted = Thread.interrupted();
248 
249         FileLock fileLock = null;
250         try {
251             fileLock = fileChannel.lock();
252             long position = fileChannel.position();
253             long size = fileChannel.size();
254             if (size != position) {
255                 fileChannel.position(size);
256             }
257             super.writeOut(event);
258         } catch (IOException e) {
259             // Mainly to catch FileLockInterruptionExceptions (see LOGBACK-875)
260             resilientFOS.postIOFailure(e);
261         } finally {
262             if (fileLock != null && fileLock.isValid()) {
263                 fileLock.release();
264             }
265 
266             // Re-interrupt if we started in an interrupted state (see LOGBACK-875)
267             if (interrupted) {
268                 Thread.currentThread().interrupt();
269             }
270         }
271     }
272 
273     @Override
274     protected void writeOut(E event) throws IOException {
275         if (prudent) {
276             safeWrite(event);
277         } else {
278             super.writeOut(event);
279         }
280     }
281 }