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