+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.log4j;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.QuietWriter;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.LoggingEvent;
+
+// Contibutors: Jens Uwe Pipka <jens.pipka@gmx.de>
+// Ben Sandee
+
+/**
+ WriterAppender appends log events to a {@link java.io.Writer} or an
+ {@link java.io.OutputStream} depending on the user's choice.
+
+ @author Ceki Gülcü
+ @since 1.1 */
+public class WriterAppender extends AppenderSkeleton {
+
+
+ /**
+ Immediate flush means that the underlying writer or output stream
+ will be flushed at the end of each append operation unless shouldFlush()
+ is overridden. Immediate
+ flush is slower but ensures that each append request is actually
+ written. If <code>immediateFlush</code> is set to
+ <code>false</code>, 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.
+
+ <p>The <code>immediateFlush</code> variable is set to
+ <code>true</code> by default.
+
+ */
+ protected boolean immediateFlush = true;
+
+ /**
+ The encoding to use when writing. <p>The
+ <code>encoding</code> variable is set to <code>null</null> by
+ default which results in the utilization of the system's default
+ encoding. */
+ protected String encoding;
+
+ /**
+ This is the {@link QuietWriter quietWriter} where we will write
+ to.
+ */
+ protected QuietWriter qw;
+
+
+ /**
+ This default constructor does nothing. */
+ public
+ WriterAppender() {
+ }
+
+ /**
+ Instantiate a WriterAppender and set the output destination to a
+ new {@link OutputStreamWriter} initialized with <code>os</code>
+ as its {@link OutputStream}. */
+ public
+ WriterAppender(Layout layout, OutputStream os) {
+ this(layout, new OutputStreamWriter(os));
+ }
+
+ /**
+ Instantiate a WriterAppender and set the output destination to
+ <code>writer</code>.
+
+ <p>The <code>writer</code> must have been previously opened by
+ the user. */
+ public
+ WriterAppender(Layout layout, Writer writer) {
+ this.layout = layout;
+ this.setWriter(writer);
+ }
+
+ /**
+ If the <b>ImmediateFlush</b> option is set to
+ <code>true</code>, the appender will flush at the end of each
+ write. This is the default behavior. If the option is set to
+ <code>false</code>, then the underlying stream can defer writing
+ to physical medium to a later time.
+
+ <p>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 <b>ImmediateFlush</b> option.
+ */
+ public
+ boolean getImmediateFlush() {
+ return immediateFlush;
+ }
+
+ /**
+ Does nothing.
+ */
+ public
+ void activateOptions() {
+ }
+
+
+ /**
+ This method is called by the {@link AppenderSkeleton#doAppend}
+ method.
+
+ <p>If the output stream exists and is writable then write a log
+ statement to the output stream. Otherwise, write a single warning
+ message to <code>System.err</code>.
+
+ <p>The format of the output will depend on this appender's
+ layout.
+
+ */
+ public
+ void append(LoggingEvent event) {
+
+ // Reminder: the nesting of calls is:
+ //
+ // doAppend()
+ // - check threshold
+ // - filter
+ // - append();
+ // - checkEntryConditions();
+ // - subAppend();
+
+ if(!checkEntryConditions()) {
+ return;
+ }
+ subAppend(event);
+ }
+
+ /**
+ This method determines if there is a sense in attempting to append.
+
+ <p>It checks whether there is a set output target and also if
+ there is a set layout. If these checks fail, then the boolean
+ value <code>false</code> is returned. */
+ protected
+ boolean checkEntryConditions() {
+ if(this.closed) {
+ LogLog.warn("Not allowed to write to a closed appender.");
+ return false;
+ }
+
+ if(this.qw == null) {
+ errorHandler.error("No output stream or file set for the appender named ["+
+ name+"].");
+ return false;
+ }
+
+ if(this.layout == null) {
+ errorHandler.error("No layout set for the appender named ["+ name+"].");
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ Close this appender instance. The underlying stream or writer is
+ also closed.
+
+ <p>Closed appenders cannot be reused.
+
+ @see #setWriter
+ @since 0.8.4 */
+ public
+ synchronized
+ void close() {
+ if(this.closed) {
+ return;
+ }
+ this.closed = true;
+ writeFooter();
+ reset();
+ }
+
+ /**
+ * Close the underlying {@link java.io.Writer}.
+ * */
+ protected void closeWriter() {
+ if(qw != null) {
+ try {
+ qw.close();
+ } catch(IOException e) {
+ if (e instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ // There is do need to invoke an error handler at this late
+ // stage.
+ LogLog.error("Could not close " + qw, e);
+ }
+ }
+ }
+
+ /**
+ Returns an OutputStreamWriter when passed an OutputStream. The
+ encoding used will depend on the value of the
+ <code>encoding</code> 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();
+ if(enc != null) {
+ try {
+ retval = new OutputStreamWriter(os, enc);
+ } catch(IOException e) {
+ if (e instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LogLog.warn("Error initializing output writer.");
+ LogLog.warn("Unsupported encoding?");
+ }
+ }
+ if(retval == null) {
+ retval = new OutputStreamWriter(os);
+ }
+ return retval;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public void setEncoding(String value) {
+ encoding = value;
+ }
+
+
+
+
+ /**
+ Set the {@link ErrorHandler} for this WriterAppender and also the
+ underlying {@link QuietWriter} if any. */
+ public synchronized void setErrorHandler(ErrorHandler eh) {
+ if(eh == null) {
+ LogLog.warn("You have tried to set a null error-handler.");
+ } else {
+ this.errorHandler = eh;
+ if(this.qw != null) {
+ this.qw.setErrorHandler(eh);
+ }
+ }
+ }
+
+ /**
+ <p>Sets the Writer where the log output will go. The
+ specified Writer must be opened by the user and be
+ writable.
+
+ <p>The <code>java.io.Writer</code> will be closed when the
+ appender instance is closed.
+
+
+ <p><b>WARNING:</b> Logging to an unopened Writer will fail.
+ <p>
+ @param writer An already opened Writer. */
+ public synchronized void setWriter(Writer writer) {
+ reset();
+ this.qw = new QuietWriter(writer, errorHandler);
+ //this.tp = new TracerPrintWriter(qw);
+ writeHeader();
+ }
+
+
+ /**
+ Actual writing occurs here.
+
+ <p>Most subclasses of <code>WriterAppender</code> will need to
+ override this method.
+
+ @since 0.9.0 */
+ protected
+ void subAppend(LoggingEvent event) {
+ this.qw.write(this.layout.format(event));
+
+ if(layout.ignoresThrowable()) {
+ String[] s = event.getThrowableStrRep();
+ if (s != null) {
+ int len = s.length;
+ for(int i = 0; i < len; i++) {
+ this.qw.write(s[i]);
+ this.qw.write(Layout.LINE_SEP);
+ }
+ }
+ }
+
+ if(shouldFlush(event)) {
+ this.qw.flush();
+ }
+ }
+
+
+
+ /**
+ The WriterAppender requires a layout. Hence, this method returns
+ <code>true</code>.
+ */
+ public
+ boolean requiresLayout() {
+ return true;
+ }
+
+ /**
+ Clear internal references to the writer and other variables.
+
+ Subclasses can override this method for an alternate closing
+ behavior. */
+ protected
+ void reset() {
+ closeWriter();
+ this.qw = null;
+ //this.tp = null;
+ }
+
+
+ /**
+ Write a footer as produced by the embedded layout's {@link
+ Layout#getFooter} method. */
+ protected
+ void writeFooter() {
+ if(layout != null) {
+ String f = layout.getFooter();
+ if(f != null && this.qw != null) {
+ this.qw.write(f);
+ this.qw.flush();
+ }
+ }
+ }
+
+ /**
+ Write a header as produced by the embedded layout's {@link
+ Layout#getHeader} method. */
+ protected
+ void writeHeader() {
+ if(layout != null) {
+ String h = layout.getHeader();
+ if(h != null && this.qw != null) {
+ this.qw.write(h);
+ }
+ }
+ }
+
+ /**
+ * Determines whether the writer should be flushed after
+ * this event is written.
+ *
+ * @since 1.2.16
+ */
+ protected boolean shouldFlush(final LoggingEvent event) {
+ return immediateFlush;
+ }
+}