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