/*
* (C) 2007-2012 Alibaba Group Holding Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Authors:
* wuhua <wq163@163.com> , boyan <killme2008@gmail.com>
*/
package com.taobao.metamorphosis.client.extension.log4j;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
/**
*
* @author wuxin
* @since 1.0, 2009-10-20 ����03:49:32
*/
public class FileStreamAppender extends StreamAppender {
/**
* Controls file truncatation. The default value for this variable is
* <code>true</code>, meaning that by default a <code>FileAppender</code>
* will append to an existing file and not truncate it.
*
* <p>
* This option is meaningful only if the FileAppender opens the file.
*/
protected boolean fileAppend = true;
/**
* The name of the log file.
*/
protected String fileName = null;
/**
* Do we do bufferedIO?
*/
protected boolean bufferedIO = false;
/**
* Determines the size of IO buffer be. Default is 8K.
*/
protected int bufferSize = 8 * 1024;
/**
* The default constructor does not do anything.
*/
public FileStreamAppender() {
}
/**
* Instantiate a <code>FileAppender</code> and open the file designated by
* <code>filename</code>. The opened filename will become the output
* destination for this appender.
*
* <p>
* If the <code>append</code> parameter is true, the file will be appended
* to. Otherwise, the file designated by <code>filename</code> will be
* truncated before being opened.
*
* <p>
* If the <code>bufferedIO</code> parameter is <code>true</code>, then
* buffered IO will be used to write to the output file.
*/
public FileStreamAppender(Layout layout, String filename, boolean append, boolean bufferedIO, int bufferSize)
throws IOException {
this.layout = layout;
this.setFile(filename, append, bufferedIO, bufferSize);
}
/**
* Instantiate a FileAppender and open the file designated by
* <code>filename</code>. The opened filename will become the output
* destination for this appender.
*
* <p>
* If the <code>append</code> parameter is true, the file will be appended
* to. Otherwise, the file designated by <code>filename</code> will be
* truncated before being opened.
*/
public FileStreamAppender(Layout layout, String filename, boolean append) throws IOException {
this.layout = layout;
this.setFile(filename, append, false, bufferSize);
}
/**
* Instantiate a FileAppender and open the file designated by
* <code>filename</code>. The opened filename will become the output
* destination for this appender.
*
* <p>
* The file will be appended to.
*/
public FileStreamAppender(Layout layout, String filename) throws IOException {
this(layout, filename, true);
}
/**
* The <b>File</b> property takes a string value which should be the name of
* the file to append to.
*
* <p>
* <font color="#DD0044"><b>Note that the special values "System.out" or
* "System.err" are no longer honored.</b></font>
*
* <p>
* Note: Actual opening of the file is made when {@link #activateOptions} is
* called, not when the options are set.
*/
public synchronized void setFile(String file) {
// Trim spaces from both ends. The users probably does not want
// trailing spaces in file names.
String val = file.trim();
fileName = val;
}
/**
* Returns the value of the <b>Append</b> option.
*/
public boolean getAppend() {
return fileAppend;
}
/** Returns the value of the <b>File</b> option. */
public synchronized String getFile() {
return fileName;
}
/**
* If the value of <b>File</b> is not <code>null</code>, then
* {@link #setFile} is called with the values of <b>File</b> and
* <b>Append</b> properties.
*
* @since 0.8.1
*/
@Override
public void activateOptions() {
if (fileName != null) {
try {
setFile(fileName, fileAppend, bufferedIO, bufferSize);
}
catch (java.io.IOException e) {
LogLog.error("setFile(" + fileName + "," + fileAppend + ") call failed.", e);
}
}
else {
// LogLog.error("File option not set for appender ["+name+"].");
LogLog.warn("File option not set for appender [" + name + "].");
LogLog.warn("Are you using FileAppender instead of ConsoleAppender?");
}
}
/**
* Closes the previously opened file.
*/
protected void closeFile() {
if (this.out != null) {
try {
this.out.close();
}
catch (java.io.IOException e) {
// Exceptionally, it does not make sense to delegate to an
// ErrorHandler. Since a closed appender is basically dead.
LogLog.error("Could not close " + out, e);
}
}
}
/**
* Get the value of the <b>BufferedIO</b> option.
*
* <p>
* BufferedIO will significatnly increase performance on heavily loaded
* systems.
*/
public boolean getBufferedIO() {
return this.bufferedIO;
}
/**
* Get the size of the IO buffer.
*/
public int getBufferSize() {
return this.bufferSize;
}
/**
* The <b>Append</b> option takes a boolean value. It is set to
* <code>true</code> by default. If true, then <code>File</code> will be
* opened in append mode by {@link #setFile setFile} (see above). Otherwise,
* {@link #setFile setFile} will open <code>File</code> in truncate mode.
*
* <p>
* Note: Actual opening of the file is made when {@link #activateOptions} is
* called, not when the options are set.
*/
public void setAppend(boolean flag) {
fileAppend = flag;
}
/**
* The <b>BufferedIO</b> option takes a boolean value. It is set to
* <code>false</code> by default. If true, then <code>File</code> will be
* opened and the resulting {@link java.io.Writer} wrapped around a
* {@link BufferedWriter}.
*
* BufferedIO will significatnly increase performance on heavily loaded
* systems.
*/
public void setBufferedIO(boolean bufferedIO) {
this.bufferedIO = bufferedIO;
if (bufferedIO) {
immediateFlush = false;
}
}
/**
* Set the size of the IO buffer.
*/
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
/**
* <p>
* Sets and <i>opens</i> the file where the log output will go. The
* specified file must be writable.
*
* <p>
* If there was already an opened file, then the previous file is closed
* first.
*
* <p>
* <b>Do not use this method directly. To configure a FileAppender or one of
* its subclasses, set its properties one by one and then call
* activateOptions.</b>
*
* @param fileName
* The path to the log file.
* @param append
* If true will append to fileName. Otherwise will truncate
* fileName.
*/
public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
throws IOException {
LogLog.debug("setFile called: " + fileName + ", " + append);
// It does not make sense to have immediate flush and bufferedIO.
if (bufferedIO) {
setImmediateFlush(false);
}
reset();
FileOutputStream ostream = null;
try {
//
// attempt to create file
//
ostream = new FileOutputStream(fileName, append);
}
catch (FileNotFoundException ex) {
//
// if parent directory does not exist then
// attempt to create it and try to create file
// see bug 9150
//
String parentName = new File(fileName).getParent();
if (parentName != null) {
File parentDir = new File(parentName);
if (!parentDir.exists() && parentDir.mkdirs()) {
ostream = new FileOutputStream(fileName, append);
}
else {
throw ex;
}
}
else {
throw ex;
}
}
if (bufferedIO) {
this.out = new BufferedOutputStream(ostream, bufferSize);
}
else {
this.out = ostream;
}
this.fileName = fileName;
this.fileAppend = append;
this.bufferedIO = bufferedIO;
this.bufferSize = bufferSize;
writeHeader();
LogLog.debug("setFile ended");
}
/**
* Close any previously opened file and call the parent's <code>reset</code>
* .
*/
@Override
protected void reset() {
closeFile();
this.fileName = null;
super.reset();
}
}