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
63       * the 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
105      * {@link #openFile} is called with the values of <b>File</b> and
106      * <b>Append</b> properties.
107      */
108     public void start() {
109         int errors = 0;
110         if (getFile() != null) {
111             addInfo("File property is set to [" + fileName + "]");
112 
113             if (prudent) {
114                 if (!isAppend()) {
115                     setAppend(true);
116                     addWarn("Setting \"Append\" property to true on account of \"Prudent\" mode");
117                 }
118             }
119 
120             if (checkForFileCollisionInPreviousFileAppenders()) {
121                 addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
122                 addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL);
123                 errors++;
124             } else {
125                 // file should be opened only if collision free
126                 try {
127                     openFile(getFile());
128                 } catch (java.io.IOException e) {
129                     errors++;
130                     addError("openFile(" + fileName + "," + append + ") call failed.", e);
131                 }
132             }
133         } else {
134             errors++;
135             addError("\"File\" property not set for appender named [" + name + "].");
136         }
137         if (errors == 0) {
138             super.start();
139         }
140     }
141 
142     @Override
143     public void stop() {
144         super.stop();
145 
146         Map<String, String> map = ContextUtil.getFilenameCollisionMap(context);
147         if (map == null || getName() == null)
148             return;
149 
150         map.remove(getName());
151     }
152 
153     protected boolean checkForFileCollisionInPreviousFileAppenders() {
154         boolean collisionsDetected = false;
155         if (fileName == null) {
156             return false;
157         }
158         @SuppressWarnings("unchecked")
159         Map<String, String> map = (Map<String, String>) context.getObject(CoreConstants.FA_FILENAME_COLLISION_MAP);
160         if (map == null) {
161             return collisionsDetected;
162         }
163         for (Entry<String, String> entry : map.entrySet()) {
164             if (fileName.equals(entry.getValue())) {
165                 addErrorForCollision("File", entry.getValue(), entry.getKey());
166                 collisionsDetected = true;
167             }
168         }
169         if (name != null) {
170             map.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 [" + appenderName + "] defined earlier.");
177     }
178 
179     /**
180      * <p>
181      * Sets and <i>opens</i> the file where the log output will go. The specified
182      * file must be writable.
183      * 
184      * <p>
185      * If there was already an opened file, then the previous file is closed
186      * first.
187      * 
188      * <p>
189      * <b>Do not use this method directly. To configure a FileAppender or one of
190      * its subclasses, set its properties one by one and then call start().</b>
191      * 
192      * @param file_name
193      *          The path to the log file.
194      */
195     public void openFile(String file_name) throws IOException {
196         lock.lock();
197         try {
198             File file = new File(file_name);
199             boolean result = FileUtil.createMissingParentDirectories(file);
200             if (!result) {
201                 addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]");
202             }
203 
204             ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append, bufferSize.getSize());
205             resilientFos.setContext(context);
206             setOutputStream(resilientFos);
207         } finally {
208             lock.unlock();
209         }
210     }
211 
212     /**
213      * @see #setPrudent(boolean)
214      * 
215      * @return true if in prudent mode
216      */
217     public boolean isPrudent() {
218         return prudent;
219     }
220 
221     /**
222      * When prudent is set to true, file appenders from multiple JVMs can safely
223      * write to the same file.
224      * 
225      * @param prudent
226      */
227     public void setPrudent(boolean prudent) {
228         this.prudent = prudent;
229     }
230 
231     public void setAppend(boolean append) {
232         this.append = append;
233     }
234     
235     public void setBufferSize(FileSize bufferSize) {
236         addInfo("Setting bufferSize to ["+bufferSize.toString()+"]");
237         this.bufferSize = bufferSize;
238     }
239 
240     private void safeWrite(E event) throws IOException {
241         ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream();
242         FileChannel fileChannel = resilientFOS.getChannel();
243         if (fileChannel == null) {
244             return;
245         }
246 
247         // Clear any current interrupt (see LOGBACK-875)
248         boolean interrupted = Thread.interrupted();
249 
250         FileLock fileLock = null;
251         try {
252             fileLock = fileChannel.lock();
253             long position = fileChannel.position();
254             long size = fileChannel.size();
255             if (size != position) {
256                 fileChannel.position(size);
257             }
258             super.writeOut(event);
259         } catch (IOException e) {
260             // Mainly to catch FileLockInterruptionExceptions (see LOGBACK-875)
261             resilientFOS.postIOFailure(e);
262         } finally {
263             if (fileLock != null && fileLock.isValid()) {
264                 fileLock.release();
265             }
266 
267             // Re-interrupt if we started in an interrupted state (see LOGBACK-875)
268             if (interrupted) {
269                 Thread.currentThread().interrupt();
270             }
271         }
272     }
273 
274     @Override
275     protected void writeOut(E event) throws IOException {
276         if (prudent) {
277             safeWrite(event);
278         } else {
279             super.writeOut(event);
280         }
281     }
282 }