001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.log4j;
019
020import org.apache.log4j.helpers.QuietWriter;
021import org.apache.log4j.spi.ErrorHandler;
022import org.apache.log4j.spi.LoggingEvent;
023import org.apache.logging.log4j.status.StatusLogger;
024
025import java.io.IOException;
026import java.io.InterruptedIOException;
027import java.io.OutputStream;
028import java.io.OutputStreamWriter;
029import java.io.Writer;
030
031
032/**
033 * WriterAppender appends log events to a {@link Writer} or an
034 * {@link OutputStream} depending on the user's choice.
035 */
036public class WriterAppender extends AppenderSkeleton {
037    private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger();
038
039    /**
040     * Immediate flush means that the underlying writer or output stream
041     * will be flushed at the end of each append operation unless shouldFlush()
042     * is overridden. Immediate
043     * flush is slower but ensures that each append request is actually
044     * written. If <code>immediateFlush</code> is set to
045     * <code>false</code>, then there is a good chance that the last few
046     * logs events are not actually written to persistent media if and
047     * when the application crashes.
048     *
049     * <p>The <code>immediateFlush</code> variable is set to
050     * <code>true</code> by default.
051     */
052    protected boolean immediateFlush = true;
053
054    /**
055     * The encoding to use when writing.  <p>The
056     * <code>encoding</code> variable is set to <code>null</null> by
057     * default which results in the utilization of the system's default
058     * encoding.
059     */
060    protected String encoding;
061
062    /**
063     * This is the {@link QuietWriter quietWriter} where we will write
064     * to.
065     */
066    protected QuietWriter qw;
067
068
069    /**
070     * This default constructor does nothing.
071     */
072    public WriterAppender() {
073    }
074
075    /**
076     * Instantiate a WriterAppender and set the output destination to a
077     * new {@link OutputStreamWriter} initialized with <code>os</code>
078     * as its {@link OutputStream}.
079     */
080    public WriterAppender(Layout layout, OutputStream os) {
081        this(layout, new OutputStreamWriter(os));
082    }
083
084    /**
085     * Instantiate a WriterAppender and set the output destination to
086     * <code>writer</code>.
087     *
088     * <p>The <code>writer</code> must have been previously opened by
089     * the user.
090     */
091    public WriterAppender(Layout layout, Writer writer) {
092        this.layout = layout;
093        this.setWriter(writer);
094    }
095
096    /**
097     * Returns value of the <b>ImmediateFlush</b> option.
098     * @return the value of the immediate flush setting.
099     */
100    public boolean getImmediateFlush() {
101        return immediateFlush;
102    }
103
104    /**
105     * If the <b>ImmediateFlush</b> option is set to
106     * <code>true</code>, the appender will flush at the end of each
107     * write. This is the default behavior. If the option is set to
108     * <code>false</code>, then the underlying stream can defer writing
109     * to physical medium to a later time.
110     *
111     * <p>Avoiding the flush operation at the end of each append results in
112     * a performance gain of 10 to 20 percent. However, there is safety
113     * tradeoff involved in skipping flushing. Indeed, when flushing is
114     * skipped, then it is likely that the last few log events will not
115     * be recorded on disk when the application exits. This is a high
116     * price to pay even for a 20% performance gain.
117     *
118     * @param value the value to set the immediate flush setting to.
119     */
120    public void setImmediateFlush(boolean value) {
121        immediateFlush = value;
122    }
123
124    /**
125     * Does nothing.
126     */
127    @Override
128    public void activateOptions() {
129    }
130
131
132    /**
133     * This method is called by the {@link AppenderSkeleton#doAppend}
134     * method.
135     *
136     * <p>If the output stream exists and is writable then write a log
137     * statement to the output stream. Otherwise, write a single warning
138     * message to <code>System.err</code>.
139     *
140     * <p>The format of the output will depend on this appender's
141     * layout.
142     */
143    @Override
144    public void append(LoggingEvent event) {
145
146        // Reminder: the nesting of calls is:
147        //
148        //    doAppend()
149        //      - check threshold
150        //      - filter
151        //      - append();
152        //        - checkEntryConditions();
153        //        - subAppend();
154
155        if (!checkEntryConditions()) {
156            return;
157        }
158        subAppend(event);
159    }
160
161    /**
162     * This method determines if there is a sense in attempting to append.
163     *
164     * <p>It checks whether there is a set output target and also if
165     * there is a set layout. If these checks fail, then the boolean
166     * value <code>false</code> is returned.
167     * @return true if appending is allowed, false otherwise.
168     */
169    protected boolean checkEntryConditions() {
170        if (this.closed) {
171            LOGGER.warn("Not allowed to write to a closed appender.");
172            return false;
173        }
174
175        if (this.qw == null) {
176            errorHandler.error("No output stream or file set for the appender named [" + name + "].");
177            return false;
178        }
179
180        if (this.layout == null) {
181            errorHandler.error("No layout set for the appender named [" + name + "].");
182            return false;
183        }
184        return true;
185    }
186
187
188    /**
189     * Close this appender instance. The underlying stream or writer is
190     * also closed.
191     *
192     * <p>Closed appenders cannot be reused.
193     *
194     * @see #setWriter
195     * @since 0.8.4
196     */
197    @Override
198    public synchronized void close() {
199        if (this.closed) {
200            return;
201        }
202        this.closed = true;
203        writeFooter();
204        reset();
205    }
206
207    /**
208     * Close the underlying {@link Writer}.
209     */
210    protected void closeWriter() {
211        if (qw != null) {
212            try {
213                qw.close();
214            } catch (IOException e) {
215                if (e instanceof InterruptedIOException) {
216                    Thread.currentThread().interrupt();
217                }
218                // There is do need to invoke an error handler at this late
219                // stage.
220                LOGGER.error("Could not close " + qw, e);
221            }
222        }
223    }
224
225    /**
226     * Returns an OutputStreamWriter when passed an OutputStream.  The
227     * encoding used will depend on the value of the
228     * <code>encoding</code> property.  If the encoding value is
229     * specified incorrectly the writer will be opened using the default
230     * system encoding (an error message will be printed to the LOGGER.
231     * @param os The OutputStream.
232     * @return The OutputStreamWriter.
233     */
234    protected OutputStreamWriter createWriter(OutputStream os) {
235        OutputStreamWriter retval = null;
236
237        String enc = getEncoding();
238        if (enc != null) {
239            try {
240                retval = new OutputStreamWriter(os, enc);
241            } catch (IOException e) {
242                if (e instanceof InterruptedIOException) {
243                    Thread.currentThread().interrupt();
244                }
245                LOGGER.warn("Error initializing output writer.");
246                LOGGER.warn("Unsupported encoding?");
247            }
248        }
249        if (retval == null) {
250            retval = new OutputStreamWriter(os);
251        }
252        return retval;
253    }
254
255    public String getEncoding() {
256        return encoding;
257    }
258
259    public void setEncoding(String value) {
260        encoding = value;
261    }
262
263
264    /**
265     * Set the {@link ErrorHandler} for this WriterAppender and also the
266     * underlying {@link QuietWriter} if any.
267     */
268    @Override
269    public synchronized void setErrorHandler(ErrorHandler eh) {
270        if (eh == null) {
271            LOGGER.warn("You have tried to set a null error-handler.");
272        } else {
273            this.errorHandler = eh;
274            if (this.qw != null) {
275                this.qw.setErrorHandler(eh);
276            }
277        }
278    }
279
280    /**
281     * <p>Sets the Writer where the log output will go. The
282     * specified Writer must be opened by the user and be
283     * writable.
284     *
285     * <p>The <code>java.io.Writer</code> will be closed when the
286     * appender instance is closed.
287     *
288     *
289     * <p><b>WARNING:</b> Logging to an unopened Writer will fail.
290     * <p>
291     *
292     * @param writer An already opened Writer.
293     */
294    public synchronized void setWriter(Writer writer) {
295        reset();
296        this.qw = new QuietWriter(writer, errorHandler);
297        //this.tp = new TracerPrintWriter(qw);
298        writeHeader();
299    }
300
301
302    /**
303     * Actual writing occurs here.
304     *
305     * <p>Most subclasses of <code>WriterAppender</code> will need to
306     * override this method.
307     * @param event The event to log.
308     *
309     * @since 0.9.0
310     */
311    protected void subAppend(LoggingEvent event) {
312        this.qw.write(this.layout.format(event));
313
314        if (layout.ignoresThrowable()) {
315            String[] s = event.getThrowableStrRep();
316            if (s != null) {
317                int len = s.length;
318                for (int i = 0; i < len; i++) {
319                    this.qw.write(s[i]);
320                    this.qw.write(Layout.LINE_SEP);
321                }
322            }
323        }
324
325        if (shouldFlush(event)) {
326            this.qw.flush();
327        }
328    }
329
330
331    /**
332     * The WriterAppender requires a layout. Hence, this method returns
333     * <code>true</code>.
334     */
335    @Override
336    public boolean requiresLayout() {
337        return true;
338    }
339
340    /**
341     * Clear internal references to the writer and other variables.
342     * <p>
343     * Subclasses can override this method for an alternate closing
344     * behavior.
345     */
346    protected void reset() {
347        closeWriter();
348        this.qw = null;
349        //this.tp = null;
350    }
351
352
353    /**
354     * Write a footer as produced by the embedded layout's {@link
355     * Layout#getFooter} method.
356     */
357    protected void writeFooter() {
358        if (layout != null) {
359            String f = layout.getFooter();
360            if (f != null && this.qw != null) {
361                this.qw.write(f);
362                this.qw.flush();
363            }
364        }
365    }
366
367    /**
368     * Write a header as produced by the embedded layout's {@link
369     * Layout#getHeader} method.
370     */
371    protected void writeHeader() {
372        if (layout != null) {
373            String h = layout.getHeader();
374            if (h != null && this.qw != null) {
375                this.qw.write(h);
376            }
377        }
378    }
379
380    /**
381     * Determines whether the writer should be flushed after
382     * this event is written.
383     * @param event The event to log.
384     * @return true if the writer should be flushed.
385     *
386     * @since 1.2.16
387     */
388    protected boolean shouldFlush(final LoggingEvent event) {
389        return immediateFlush;
390    }
391}