001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.log4j;
019
020import java.io.BufferedWriter;
021import java.io.File;
022import java.io.FileNotFoundException;
023import java.io.FileOutputStream;
024import java.io.IOException;
025import java.io.InterruptedIOException;
026import java.io.Writer;
027
028import org.apache.log4j.helpers.LogLog;
029import org.apache.log4j.helpers.QuietWriter;
030import org.apache.log4j.spi.ErrorCode;
031
032// Contibutors: Jens Uwe Pipka <jens.pipka@gmx.de>
033//              Ben Sandee
034
035/**
036 *  FileAppender appends log events to a file.
037 *
038 *  <p>Support for <code>java.io.Writer</code> and console appending
039 *  has been deprecated and then removed. See the replacement
040 *  solutions: {@link WriterAppender} and {@link ConsoleAppender}.
041 *
042 * @author Ceki G&uuml;lc&uuml; 
043 * */
044public class FileAppender extends WriterAppender {
045
046  /** Controls file truncatation. The default value for this variable
047   * is <code>true</code>, meaning that by default a
048   * <code>FileAppender</code> will append to an existing file and not
049   * truncate it.
050   *
051   * <p>This option is meaningful only if the FileAppender opens the
052   * file.
053   */
054  protected boolean fileAppend = true;
055
056  /**
057     The name of the log file. */
058  protected String fileName = null;
059
060  /**
061     Do we do bufferedIO? */
062  protected boolean bufferedIO = false;
063
064  /**
065   * Determines the size of IO buffer be. Default is 8K. 
066   */
067  protected int bufferSize = 8*1024;
068
069
070  /**
071     The default constructor does not do anything.
072  */
073  public
074  FileAppender() {
075  }
076
077  /**
078    Instantiate a <code>FileAppender</code> and open the file
079    designated by <code>filename</code>. The opened filename will
080    become the output destination for this appender.
081
082    <p>If the <code>append</code> parameter is true, the file will be
083    appended to. Otherwise, the file designated by
084    <code>filename</code> will be truncated before being opened.
085
086    <p>If the <code>bufferedIO</code> parameter is <code>true</code>,
087    then buffered IO will be used to write to the output file.
088
089  */
090  public
091  FileAppender(Layout layout, String filename, boolean append, boolean bufferedIO,
092               int bufferSize) throws IOException {
093    this.layout = layout;
094    this.setFile(filename, append, bufferedIO, bufferSize);
095  }
096
097  /**
098    Instantiate a FileAppender and open the file designated by
099    <code>filename</code>. The opened filename will become the output
100    destination for this appender.
101
102    <p>If the <code>append</code> parameter is true, the file will be
103    appended to. Otherwise, the file designated by
104    <code>filename</code> will be truncated before being opened.
105  */
106  public
107  FileAppender(Layout layout, String filename, boolean append)
108                                                             throws IOException {
109    this.layout = layout;
110    this.setFile(filename, append, false, bufferSize);
111  }
112
113  /**
114     Instantiate a FileAppender and open the file designated by
115    <code>filename</code>. The opened filename will become the output
116    destination for this appender.
117
118    <p>The file will be appended to.  */
119  public
120  FileAppender(Layout layout, String filename) throws IOException {
121    this(layout, filename, true);
122  }
123
124  /**
125     The <b>File</b> property takes a string value which should be the
126     name of the file to append to.
127
128     <p><font color="#DD0044"><b>Note that the special values
129     "System.out" or "System.err" are no longer honored.</b></font>
130
131     <p>Note: Actual opening of the file is made when {@link
132     #activateOptions} is called, not when the options are set.  */
133  public void setFile(String file) {
134    // Trim spaces from both ends. The users probably does not want
135    // trailing spaces in file names.
136    String val = file.trim();
137    fileName = val;
138  }
139
140  /**
141      Returns the value of the <b>Append</b> option.
142   */
143  public
144  boolean getAppend() {
145    return fileAppend;
146  }
147
148
149  /** Returns the value of the <b>File</b> option. */
150  public
151  String getFile() {
152    return fileName;
153  }
154
155  /**
156     If the value of <b>File</b> is not <code>null</code>, then {@link
157     #setFile} is called with the values of <b>File</b>  and
158     <b>Append</b> properties.
159
160     @since 0.8.1 */
161  public
162  void activateOptions() {
163    if(fileName != null) {
164      try {
165        setFile(fileName, fileAppend, bufferedIO, bufferSize);
166      }
167      catch(java.io.IOException e) {
168        errorHandler.error("setFile("+fileName+","+fileAppend+") call failed.",
169                           e, ErrorCode.FILE_OPEN_FAILURE);
170      }
171    } else {
172      //LogLog.error("File option not set for appender ["+name+"].");
173      LogLog.warn("File option not set for appender ["+name+"].");
174      LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
175    }
176  }
177
178 /**
179     Closes the previously opened file.
180  */
181  protected
182  void closeFile() {
183    if(this.qw != null) {
184      try {
185        this.qw.close();
186      }
187      catch(java.io.IOException e) {
188          if (e instanceof InterruptedIOException) {
189              Thread.currentThread().interrupt();
190          }
191        // Exceptionally, it does not make sense to delegate to an
192        // ErrorHandler. Since a closed appender is basically dead.
193        LogLog.error("Could not close " + qw, e);
194      }
195    }
196  }
197
198  /**
199     Get the value of the <b>BufferedIO</b> option.
200
201     <p>BufferedIO will significatnly increase performance on heavily
202     loaded systems.
203
204  */
205  public
206  boolean getBufferedIO() {
207    return this.bufferedIO;
208  }
209
210
211  /**
212     Get the size of the IO buffer.
213  */
214  public
215  int getBufferSize() {
216    return this.bufferSize;
217  }
218
219
220
221  /**
222     The <b>Append</b> option takes a boolean value. It is set to
223     <code>true</code> by default. If true, then <code>File</code>
224     will be opened in append mode by {@link #setFile setFile} (see
225     above). Otherwise, {@link #setFile setFile} will open
226     <code>File</code> in truncate mode.
227
228     <p>Note: Actual opening of the file is made when {@link
229     #activateOptions} is called, not when the options are set.
230   */
231  public
232  void setAppend(boolean flag) {
233    fileAppend = flag;
234  }
235
236  /**
237     The <b>BufferedIO</b> option takes a boolean value. It is set to
238     <code>false</code> by default. If true, then <code>File</code>
239     will be opened and the resulting {@link java.io.Writer} wrapped
240     around a {@link BufferedWriter}.
241
242     BufferedIO will significatnly increase performance on heavily
243     loaded systems.
244
245  */
246  public
247  void setBufferedIO(boolean bufferedIO) {
248    this.bufferedIO = bufferedIO;
249    if(bufferedIO) {
250      immediateFlush = false;
251    }
252  }
253
254
255  /**
256     Set the size of the IO buffer.
257  */
258  public
259  void setBufferSize(int bufferSize) {
260    this.bufferSize = bufferSize;
261  }
262
263  /**
264    <p>Sets and <i>opens</i> the file where the log output will
265    go. The specified file must be writable.
266
267    <p>If there was already an opened file, then the previous file
268    is closed first.
269
270    <p><b>Do not use this method directly. To configure a FileAppender
271    or one of its subclasses, set its properties one by one and then
272    call activateOptions.</b>
273
274    @param fileName The path to the log file.
275    @param append   If true will append to fileName. Otherwise will
276        truncate fileName.  */
277  public
278  synchronized
279  void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
280                                                            throws IOException {
281    LogLog.debug("setFile called: "+fileName+", "+append);
282
283    // It does not make sense to have immediate flush and bufferedIO.
284    if(bufferedIO) {
285      setImmediateFlush(false);
286    }
287
288    reset();
289    FileOutputStream ostream = null;
290    try {
291          //
292          //   attempt to create file
293          //
294          ostream = new FileOutputStream(fileName, append);
295    } catch(FileNotFoundException ex) {
296          //
297          //   if parent directory does not exist then
298          //      attempt to create it and try to create file
299          //      see bug 9150
300          //
301          String parentName = new File(fileName).getParent();
302          if (parentName != null) {
303             File parentDir = new File(parentName);
304             if(!parentDir.exists() && parentDir.mkdirs()) {
305                ostream = new FileOutputStream(fileName, append);
306             } else {
307                throw ex;
308             }
309          } else {
310             throw ex;
311          }
312    }
313    Writer fw = createWriter(ostream);
314    if(bufferedIO) {
315      fw = new BufferedWriter(fw, bufferSize);
316    }
317    this.setQWForFiles(fw);
318    this.fileName = fileName;
319    this.fileAppend = append;
320    this.bufferedIO = bufferedIO;
321    this.bufferSize = bufferSize;
322    writeHeader();
323    LogLog.debug("setFile ended");
324  }
325
326
327  /**
328     Sets the quiet writer being used.
329
330     This method is overriden by {@link RollingFileAppender}.
331   */
332  protected
333  void setQWForFiles(Writer writer) {
334     this.qw = new QuietWriter(writer, errorHandler);
335  }
336
337
338  /**
339     Close any previously opened file and call the parent's
340     <code>reset</code>.  */
341  protected
342  void reset() {
343    closeFile();
344    this.fileName = null;
345    super.reset();
346  }
347}
348