ch.qos.logback.core.WriterAppender Maven / Gradle / Ivy
/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2009, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.core;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import ch.qos.logback.core.status.ErrorStatus;
/**
* WriterAppender appends events to a hava.io.Writer. This class provides basic
* services that other appenders build upon.
*
* For more information about this appender, please refer to the online manual
* at http://logback.qos.ch/manual/appenders.html#WriterAppender
*
* @author Ceki Gülcü
*/
public class WriterAppender extends UnsynchronizedAppenderBase {
/**
* Immediate flush means that the underlying writer or output stream will be
* flushed at the end of each append operation. Immediate flush is slower but
* ensures that each append request is actually written. If
* immediateFlush
is set to false
, then there
* is a good chance that the last few logs events are not actually written to
* persistent media if and when the application crashes.
*
*
* The immediateFlush
variable is set to true
by
* default.
*/
private boolean immediateFlush = true;
/**
* The encoding to use when opening an InputStream.
*
* The encoding
variable is set to null by default
* which results in the use of the system's default encoding.
*/
private String encoding;
/**
* This is the {@link Writer Writer} where we will write to.
*/
private Writer writer;
/**
* The default constructor does nothing.
*/
public WriterAppender() {
}
/**
* If the ImmediateFlush option is set to true
, the
* appender will flush at the end of each write. This is the default behavior.
* If the option is set to false
, then the underlying stream
* can defer writing to physical medium to a later time.
*
* Avoiding the flush operation at the end of each append results in a
* performance gain of 10 to 20 percent. However, there is safety tradeoff
* involved in skipping flushing. Indeed, when flushing is skipped, then it is
* likely that the last few log events will not be recorded on disk when the
* application exits. This is a high price to pay even for a 20% performance
* gain.
*/
public void setImmediateFlush(boolean value) {
immediateFlush = value;
}
/**
* Returns value of the ImmediateFlush option.
*/
public boolean getImmediateFlush() {
return immediateFlush;
}
/**
* Checks that requires parameters are set and if everything is in order,
* activates this appender.
*/
public void start() {
int errors = 0;
if (this.layout == null) {
addStatus(new ErrorStatus("No layout set for the appender named \""
+ name + "\".", this));
errors++;
}
if (this.writer == null) {
addStatus(new ErrorStatus("No writer set for the appender named \""
+ name + "\".", this));
errors++;
}
// only error free appenders should be activated
if (errors == 0) {
super.start();
}
}
@Override
protected void append(E eventObject) {
if (!isStarted()) {
return;
}
subAppend(eventObject);
}
/**
* Stop this appender instance. The underlying stream or writer is also
* closed.
*
*
* Stopped appenders cannot be reused.
*/
public synchronized void stop() {
closeWriter();
super.stop();
}
/**
* Close the underlying {@link java.io.Writer}.
*/
protected void closeWriter() {
if (this.writer != null) {
try {
// before closing we have to output out layout's footer
writeFooter();
this.writer.close();
this.writer = null;
} catch (IOException e) {
addStatus(new ErrorStatus("Could not close writer for WriterAppener.",
this, e));
}
}
}
/**
* Returns an OutputStreamWriter when passed an OutputStream. The encoding
* used will depend on the value of the encoding
property. If
* the encoding value is specified incorrectly the writer will be opened using
* the default system encoding (an error message will be printed to the
* loglog.
*/
protected OutputStreamWriter createWriter(OutputStream os) {
OutputStreamWriter retval = null;
String enc = getEncoding();
try {
if (enc != null) {
retval = new OutputStreamWriter(os, enc);
} else {
retval = new OutputStreamWriter(os);
}
} catch (IOException e) {
addStatus(new ErrorStatus("Error initializing output writer.", this, e));
if (enc != null) {
addStatus(new ErrorStatus("Unsupported encoding?", this));
}
}
return retval;
}
public String getEncoding() {
return encoding;
}
public void setEncoding(String value) {
encoding = value;
}
void writeHeader() {
if (layout != null && (this.writer != null)) {
try {
StringBuilder sb = new StringBuilder();
appendIfNotNull(sb, layout.getFileHeader());
appendIfNotNull(sb, layout.getPresentationHeader());
if (sb.length() > 0) {
sb.append(CoreConstants.LINE_SEPARATOR);
// If at least one of file header or presentation header were not
// null, then append a line separator.
// This should be useful in most cases and should not hurt.
writerWrite(sb.toString(), true);
}
} catch (IOException ioe) {
this.started = false;
addStatus(new ErrorStatus("Failed to write header for appender named ["
+ name + "].", this, ioe));
}
}
}
private void appendIfNotNull(StringBuilder sb, String s) {
if (s != null) {
sb.append(s);
}
}
void writeFooter() {
if (layout != null && this.writer != null) {
try {
StringBuilder sb = new StringBuilder();
appendIfNotNull(sb, layout.getPresentationFooter());
appendIfNotNull(sb, layout.getFileFooter());
if (sb.length() > 0) {
writerWrite(sb.toString(), true); // force flush
}
} catch (IOException ioe) {
this.started = false;
addStatus(new ErrorStatus("Failed to write footer for appender named ["
+ name + "].", this, ioe));
}
}
}
/**
*
* Sets the Writer where the log output will go. The specified Writer must be
* opened by the user and be writable. The java.io.Writer
will
* be closed when the appender instance is closed.
*
* @param writer
* An already opened Writer.
*/
public synchronized void setWriter(Writer writer) {
// close any previously opened writer
closeWriter();
this.writer = writer;
writeHeader();
}
protected void writerWrite(String s, boolean flush) throws IOException {
this.writer.write(s);
if (flush) {
this.writer.flush();
}
}
/**
* Actual writing occurs here.
*
* Most subclasses of WriterAppender
will need to override this
* method.
*
* @since 0.9.0
*/
protected void subAppend(E event) {
if (!isStarted()) {
return;
}
try {
String output = this.layout.doLayout(event);
synchronized (this) {
writerWrite(output, this.immediateFlush);
}
} catch (IOException ioe) {
// as soon as an exception occurs, move to non-started state
// and add a single ErrorStatus to the SM.
this.started = false;
addStatus(new ErrorStatus("IO failure in appender", this, ioe));
}
}
}