View Javadoc

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2009, 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 java.io.BufferedWriter;
17  import java.io.File;
18  import java.io.FileOutputStream;
19  import java.io.IOException;
20  import java.io.Writer;
21  import java.nio.channels.FileChannel;
22  import java.nio.channels.FileLock;
23  
24  import ch.qos.logback.core.util.FileUtil;
25  
26  /**
27   * FileAppender appends log events to a file.
28   * 
29   * For more information about this appender, please refer to the online manual
30   * at http://logback.qos.ch/manual/appenders.html#FileAppender
31   * 
32   * @author Ceki Gülcü
33   */
34  public class FileAppender<E> extends WriterAppender<E> {
35  
36    /**
37     * Append to or truncate the file? The default value for this variable is
38     * <code>true</code>, meaning that by default a <code>FileAppender</code>
39     * will append to an existing file and not truncate it.
40     */
41    protected boolean append = true;
42  
43    /**
44     * The name of the active log file.
45     */
46    protected String fileName = null;
47  
48    /**
49     * Do we do bufferedIO?
50     */
51    protected boolean bufferedIO = false;
52  
53    /**
54     * The size of the IO buffer. Default is 8K.
55     */
56    protected int bufferSize = 8 * 1024;
57  
58    private boolean prudent = false;
59  
60    private FileChannel fileChannel = null;
61  
62    /**
63     * As in most cases, the default constructor does nothing.
64     */
65    public FileAppender() {
66    }
67  
68    /**
69     * The <b>File</b> property takes a string value which should be the name of
70     * the file to append to.
71     */
72    public void setFile(String file) {
73      if (file == null) {
74        fileName = file;
75      } else {
76        // Trim spaces from both ends. The users probably does not want
77        // trailing spaces in file names.
78        String val = file.trim();
79        fileName = val;
80      }
81    }
82  
83    /**
84     * @deprecated Use isAppend instead
85     */
86    public boolean getAppend() {
87      return append;
88    }
89  
90    /**
91     * Returns the value of the <b>Append</b> property.
92     */
93    public boolean isAppend() {
94      return append;
95    }
96  
97    /**
98     * This method is used by derived classes to obtain the raw file property.
99     * Regular users should not be calling this method.
100    * 
101    * @return the value of the file property
102    */
103   final public String rawFileProperty() {
104     return fileName;
105   }
106 
107   /**
108    * Returns the value of the <b>File</b> property.
109    * 
110    * <p>This method may be overridden by derived classes.
111    * 
112    */
113   public String getFile() {
114     return fileName;
115   }
116 
117   /**
118    * If the value of <b>File</b> is not <code>null</code>, then
119    * {@link #openFile} is called with the values of <b>File</b> and <b>Append</b>
120    * properties.
121    */
122   public void start() {
123     int errors = 0;
124     if (getFile() != null) {
125       addInfo("File property is set to [" + fileName + "]");
126 
127       if (prudent) {
128         if (isAppend() == false) {
129           setAppend(true);
130           addWarn("Setting \"Append\" property to true on account of \"Prudent\" mode");
131         }
132         if (getImmediateFlush() == false) {
133           setImmediateFlush(true);
134           addWarn("Setting \"ImmediateFlush\" to true on account of \"Prudent\" mode");
135         }
136 
137         if (bufferedIO == true) {
138           setBufferedIO(false);
139           addWarn("Setting \"BufferedIO\" property to false on account of \"Prudent\" mode");
140         }
141       }
142 
143       // In case both bufferedIO and immediateFlush are set, the former
144       // takes priority because 'immediateFlush' is set to true by default.
145       // If the user explicitly set bufferedIO, then we should follow her
146       // directives.
147       if (bufferedIO) {
148         setImmediateFlush(false);
149         addInfo("Setting \"ImmediateFlush\" property to false on account of \"bufferedIO\" property");
150       }
151 
152       try {
153         openFile(getFile());
154       } catch (java.io.IOException e) {
155         errors++;
156         addError("openFile(" + fileName + "," + append + ") call failed.", e);
157       }
158     } else {
159       errors++;
160       addError("\"File\" property not set for appender named [" + name + "].");
161     }
162     if (errors == 0) {
163       super.start();
164     }
165   }
166 
167   /**
168    * <p> Sets and <i>opens</i> the file where the log output will go. The
169    * specified file must be writable.
170    * 
171    * <p> If there was already an opened file, then the previous file is closed
172    * first.
173    * 
174    * <p> <b>Do not use this method directly. To configure a FileAppender or one
175    * of its subclasses, set its properties one by one and then call start().</b>
176    * 
177    * @param filename
178    *                The path to the log file.
179    * @param append
180    *                If true will append to fileName. Otherwise will truncate
181    *                fileName.
182    * @param bufferedIO
183    * @param bufferSize
184    * 
185    * @throws IOException
186    * 
187    */
188   public synchronized void openFile(String file_name) throws IOException {
189     File file = new File(file_name);
190     if (FileUtil.mustCreateParentDirectories(file)) {
191       boolean result = FileUtil.createMissingParentDirectories(file);
192       if (!result) {
193         addError("Failed to create parent directories for ["
194             + file.getAbsolutePath() + "]");
195       }
196     }
197 
198     FileOutputStream fileOutputStream = new FileOutputStream(file_name, append);
199     if (prudent) {
200       fileChannel = fileOutputStream.getChannel();
201     }
202     Writer w = createWriter(fileOutputStream);
203     if (bufferedIO) {
204       w = new BufferedWriter(w, bufferSize);
205     }
206     setWriter(w);
207   }
208 
209   public boolean isBufferedIO() {
210     return bufferedIO;
211   }
212 
213   public void setBufferedIO(boolean bufferedIO) {
214     this.bufferedIO = bufferedIO;
215   }
216 
217   public int getBufferSize() {
218     return bufferSize;
219   }
220 
221   public void setBufferSize(int bufferSize) {
222     this.bufferSize = bufferSize;
223   }
224 
225   /**
226    * @see #setPrudent(boolean)
227    * 
228    * @return true if in prudent mode
229    */
230   public boolean isPrudent() {
231     return prudent;
232   }
233 
234   /**
235    * When prudent is set to true, file appenders from multiple JVMs can safely
236    * write to the same file.
237    * 
238    * @param prudent
239    */
240   public void setPrudent(boolean prudent) {
241     this.prudent = prudent;
242   }
243 
244   public void setAppend(boolean append) {
245     this.append = append;
246   }
247 
248   final private void safeWrite(String s) throws IOException {
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.writerWrite(s, true);
258     } finally {
259       if (fileLock != null) {
260         fileLock.release();
261       }
262     }
263   }
264 
265   @Override
266   protected void writerWrite(String s, boolean flush) throws IOException {
267     if (prudent && fileChannel != null) {
268       safeWrite(s);
269     } else {
270       super.writerWrite(s, flush);
271     }
272   }
273 }