ch.qos.logback.core.OutputStreamAppender Maven / Gradle / Ivy
/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2015, 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 static ch.qos.logback.core.CoreConstants.CODES_URL;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.locks.ReentrantLock;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.rolling.LengthCounter;
import ch.qos.logback.core.spi.DeferredProcessingAware;
import ch.qos.logback.core.status.ErrorStatus;
/**
* OutputStreamAppender appends events to a {@link OutputStream}. 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#OutputStreamAppender
*
* @author Ceki Gülcü
*/
public class OutputStreamAppender extends UnsynchronizedAppenderBase {
/**
* It is the encoder which is ultimately responsible for writing the event to an
* {@link OutputStream}.
*/
protected Encoder encoder;
/**
* All synchronization in this class is done via the lock object.
*/
protected final ReentrantLock streamWriteLock = new ReentrantLock(false);
/**
* This is the {@link OutputStream outputStream} where output will be written.
*/
private OutputStream outputStream;
boolean immediateFlush = true;
/**
* The underlying output stream used by this appender.
*
* @return
*/
public OutputStream getOutputStream() {
return outputStream;
}
/**
* Checks that requires parameters are set and if everything is in order,
* activates this appender.
*/
public void start() {
int errors = 0;
if (this.encoder == null) {
addStatus(new ErrorStatus("No encoder set for the appender named \"" + name + "\".", this));
errors++;
}
if (this.outputStream == null) {
addStatus(new ErrorStatus("No output stream set for the appender named \"" + name + "\".", this));
errors++;
}
if (encoder == null) {
addWarn("Encoder has not been set. Cannot invoke its init method.");
errors++;
}
// only error free appenders should be activated
if (errors == 0) {
super.start();
encoderInit();
}
}
public void setLayout(Layout layout) {
addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead.");
addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.");
addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details");
LayoutWrappingEncoder lwe = new LayoutWrappingEncoder();
lwe.setLayout(layout);
lwe.setContext(context);
this.encoder = lwe;
}
@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 void stop() {
if(!isStarted())
return;
streamWriteLock.lock();
try {
closeOutputStream();
super.stop();
} finally {
streamWriteLock.unlock();
}
}
/**
* Close the underlying {@link OutputStream}.
*/
protected void closeOutputStream() {
if (this.outputStream != null) {
try {
// before closing we have to output out encooder's footer
encoderClose();
this.outputStream.close();
this.outputStream = null;
} catch (IOException e) {
addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e));
}
}
}
void encoderClose() {
if (encoder != null && this.outputStream != null) {
try {
byte[] footer = encoder.footerBytes();
writeBytes(footer);
} catch (IOException ioe) {
this.started = false;
addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe));
}
}
}
/**
*
* Sets the @link OutputStream} where the log output will go. The specified
* OutputStream
must be opened by the user and be writable. The
* OutputStream
will be closed when the appender instance is
* closed.
*
* @param outputStream An already opened OutputStream.
*/
public void setOutputStream(OutputStream outputStream) {
streamWriteLock.lock();
try {
// close any previously opened output stream
closeOutputStream();
this.outputStream = outputStream;
// the first call to setOutputStream() is made on a non started appender
// However, in subsequent calls to setOutputStream() the appender will be in
// started state. In subsequent calls, in particular when opening a file after rollover,
// we have to output the header hence the call to encoderInit().
if(isStarted()) {
encoderInit();
}
} finally {
streamWriteLock.unlock();
}
}
void encoderInit() {
if (encoder != null && this.outputStream != null) {
try {
byte[] header = encoder.headerBytes();
writeBytes(header);
} catch (IOException ioe) {
this.started = false;
addStatus(
new ErrorStatus("Failed to initialize encoder for appender named [" + name + "].", this, ioe));
}
}
}
protected void writeOut(E event) throws IOException {
byte[] byteArray = this.encoder.encode(event);
writeBytes(byteArray);
}
private void writeBytes(byte[] byteArray) throws IOException {
if (byteArray == null || byteArray.length == 0)
return;
streamWriteLock.lock();
try {
// guard against appender having been stop() in parallel
// note that the encoding step is performed outside the protection of the streamWriteLock
if(isStarted()) {
writeByteArrayToOutputStreamWithPossibleFlush(byteArray);
updateByteCount(byteArray);
}
} finally {
streamWriteLock.unlock();
}
}
protected void updateByteCount(byte[] byteArray) {
}
/**
* A simple method to write to an outputStream and flush the stream if immediateFlush is set to true.
*
* @since 1.3.9/1.4.9
*/
protected final void writeByteArrayToOutputStreamWithPossibleFlush(byte[] byteArray) throws IOException {
this.outputStream.write(byteArray);
if (immediateFlush) {
this.outputStream.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 {
// this step avoids LBCLASSIC-139
if (event instanceof DeferredProcessingAware) {
((DeferredProcessingAware) event).prepareForDeferredProcessing();
}
writeOut(event);
} 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));
}
}
public Encoder getEncoder() {
return encoder;
}
public void setEncoder(Encoder encoder) {
this.encoder = encoder;
}
public boolean isImmediateFlush() {
return immediateFlush;
}
public void setImmediateFlush(boolean immediateFlush) {
this.immediateFlush = immediateFlush;
}
}