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 */
017package org.apache.logging.log4j.io;
018
019import java.io.InputStream;
020import java.io.OutputStream;
021import java.io.PrintStream;
022import java.io.PrintWriter;
023import java.io.Reader;
024import java.io.UnsupportedEncodingException;
025import java.io.Writer;
026import java.nio.charset.Charset;
027import java.util.Objects;
028
029import org.apache.logging.log4j.Level;
030import org.apache.logging.log4j.LogManager;
031import org.apache.logging.log4j.Logger;
032import org.apache.logging.log4j.LoggingException;
033import org.apache.logging.log4j.Marker;
034import org.apache.logging.log4j.spi.ExtendedLogger;
035import org.apache.logging.log4j.util.StackLocatorUtil;
036
037/**
038 * Builder class to wrap {@link Logger Loggers} into Java IO compatible classes.
039 *
040 * <p>Both the {@link InputStream}/{@link OutputStream} and {@link Reader}/{@link Writer} family of classes are
041 * supported. {@link OutputStream} and {@link Writer} instances can be wrapped by a filtered version of their
042 * corresponding classes ({@link java.io.FilterOutputStream} and {@link java.io.FilterWriter}) in order to log all
043 * lines written to these instances. {@link InputStream} and {@link Reader} instances can be wrapped by a sort of
044 * wiretapped version of their respective classes; all lines read from these instances will be logged.</p>
045 *
046 * <p>The main feature, however, is the ability to create a {@link PrintWriter}, {@link PrintStream}, {@link Writer},
047 * {@link java.io.BufferedWriter}, {@link OutputStream}, or {@link java.io.BufferedOutputStream} that is backed by a
048 * {@link Logger}. The main inspiration for this feature is the JDBC API which uses a PrintWriter to perform debug
049 * logging. In order to properly integrate APIs like JDBC into Log4j, create a PrintWriter using this class.</p>
050 *
051 * <p>The IoBuilder support configuration of the logging {@link Level} it should use (defaults to the level of
052 * the underlying Logger), and an optional {@link Marker}. The other configurable objects are explained in more
053 * detail below.</p>
054 *
055 * @since 2.1
056 */
057public class IoBuilder {
058    private final ExtendedLogger logger;
059    private Level level;
060    private Marker marker;
061    private String fqcn;
062    private boolean autoFlush;
063    private boolean buffered;
064    private int bufferSize;
065    private Charset charset;
066    private Reader reader;
067    private Writer writer;
068    private InputStream inputStream;
069    private OutputStream outputStream;
070
071    /**
072     * Creates a new builder for a given {@link Logger}. The Logger instance must implement {@link ExtendedLogger} or
073     * an exception will be thrown.
074     *
075     * @param logger the Logger to wrap into a LoggerStream
076     * @return a new IoBuilder
077     * @throws UnsupportedOperationException if {@code logger} does not implement {@link ExtendedLogger} or if
078     *                                       {@code logger} is {@code null}
079     */
080    public static IoBuilder forLogger(final Logger logger) {
081        return new IoBuilder(logger);
082    }
083
084    /**
085     * Creates a new builder using a Logger name. The name provided is used to get a Logger from
086     * {@link LogManager#getLogger(String)} which will be wrapped into a LoggerStream.
087     *
088     * @param loggerName the name of the Logger to wrap into a LoggerStream
089     * @return a new IoBuilder
090     */
091    public static IoBuilder forLogger(final String loggerName) {
092        return new IoBuilder(LogManager.getLogger(loggerName));
093    }
094
095    /**
096     * Creates a new builder using a Logger named after a given Class. The Class provided is used to get a Logger from
097     * {@link LogManager#getLogger(Class)} which will be wrapped into a LoggerStream.
098     *
099     * @param clazz the Class to use as the Logger name to wrap into a LoggerStream
100     * @return a new IoBuilder
101     */
102    public static IoBuilder forLogger(final Class<?> clazz) {
103        return new IoBuilder(LogManager.getLogger(clazz));
104    }
105
106    /**
107     * Creates a new builder using a Logger named after the calling Class. This is equivalent to the following:
108     * <pre>
109     *     IoBuilder builder = IoBuilder.forLogger(LogManager.getLogger());
110     * </pre>
111     *
112     * @return a new IoBuilder
113     */
114    public static IoBuilder forLogger() {
115        return new IoBuilder(LogManager.getLogger(StackLocatorUtil.getCallerClass(2)));
116    }
117
118    /**
119     * Constructs a new IoBuilder for the given Logger. This method is provided for extensibility of this builder
120     * class. The static factory methods should be used normally.
121     *
122     * @param logger the {@link ExtendedLogger} to wrap
123     */
124    protected IoBuilder(final Logger logger) {
125        if (!(logger instanceof ExtendedLogger)) {
126            throw new UnsupportedOperationException("The provided Logger [" + String.valueOf(logger) +
127                "] does not implement " + ExtendedLogger.class.getName());
128        }
129        this.logger = (ExtendedLogger) logger;
130    }
131
132    /**
133     * Specifies the {@link Level} to log at. If no Level is configured, then the Level of the wrapped Logger will be
134     * used.
135     *
136     * @param level the Level to use for logging
137     * @return {@code this}
138     */
139    public IoBuilder setLevel(final Level level) {
140        this.level = level;
141        return this;
142    }
143
144    /**
145     * Specifies an optional {@link Marker} to use in all logging messages. If no Marker is specified, then no Marker
146     * will be used.
147     *
148     * @param marker the Marker to associate with all logging messages
149     * @return {@code this}
150     */
151    public IoBuilder setMarker(final Marker marker) {
152        this.marker = marker;
153        return this;
154    }
155
156    /**
157     * Specifies the fully qualified class name of the IO wrapper class implementation. This method should only be
158     * used when making significant extensions to the provided classes in this component and is normally unnecessary.
159     *
160     * @param fqcn the fully qualified class name of the IO wrapper class being built
161     * @return {@code this}
162     */
163    public IoBuilder setWrapperClassName(final String fqcn) {
164        this.fqcn = fqcn;
165        return this;
166    }
167
168    /**
169     * Indicates whether or not a built {@link PrintWriter} or {@link PrintStream} should automatically flush when
170     * one of the {@code println}, {@code printf}, or {@code format} methods are invoked, or when a new line character
171     * is printed.
172     *
173     * @param autoFlush if {@code true}, then {@code println}, {@code printf}, and {@code format} will auto flush
174     * @return {@code this}
175     */
176    public IoBuilder setAutoFlush(final boolean autoFlush) {
177        this.autoFlush = autoFlush;
178        return this;
179    }
180
181    /**
182     * Enables or disables using a buffered variant of the desired IO class. If this is set to {@code true}, then the
183     * instances returned by {@link #buildReader()} and {@link #buildInputStream()} can be safely cast (if necessary)
184     * to {@link java.io.BufferedReader} and {@link java.io.BufferedInputStream} respectively. This option does not
185     * have any effect on the other built variants.
186     *
187     * @param buffered indicates whether or not a wrapped {@link InputStream} or {@link Reader} should be buffered
188     * @return {@code this}
189     */
190    public IoBuilder setBuffered(final boolean buffered) {
191        this.buffered = buffered;
192        return this;
193    }
194
195    /**
196     * Configures the buffer size to use when building a {@link java.io.BufferedReader} or
197     * {@link java.io.BufferedInputStream} LoggerStream.
198     *
199     * @param bufferSize the buffer size to use or a non-positive integer to use the default size
200     * @return {@code this}
201     */
202    public IoBuilder setBufferSize(final int bufferSize) {
203        this.bufferSize = bufferSize;
204        return this;
205    }
206
207    /**
208     * Specifies the character set to use when building an {@link InputStream}, {@link OutputStream}, or
209     * {@link PrintStream}. If no character set is specified, then {@link java.nio.charset.Charset#defaultCharset()}
210     * is used.
211     *
212     * @param charset the character set to use when building an InputStream, OutputStream, or PrintStream
213     * @return {@code this}
214     */
215    public IoBuilder setCharset(final Charset charset) {
216        this.charset = charset;
217        return this;
218    }
219
220    /**
221     * Configures a {@link Reader} to be wiretapped when building a Reader. This must be set to a non-{@code null}
222     * value in order to call {@link #buildReader()}.
223     *
224     * @param reader the Reader to wiretap
225     * @return {@code this}
226     */
227    public IoBuilder filter(final Reader reader) {
228        this.reader = reader;
229        return this;
230    }
231
232    /**
233     * Configures a {@link Writer} to be written to in addition to the underlying Logger. If no Writer is specified,
234     * then the built Writer or PrintWriter will only write to the underlying Logger.
235     *
236     * @param writer the Writer to write to in addition to the Logger
237     * @return {@code this}
238     */
239    public IoBuilder filter(final Writer writer) {
240        this.writer = writer;
241        return this;
242    }
243
244    /**
245     * Configures an {@link InputStream} to be wiretapped when building an InputStream. This must be set to a
246     * non-{@code null} value in order to call {@link #buildInputStream()}.
247     *
248     * @param inputStream the InputStream to wiretap
249     * @return {@code this}
250     */
251    public IoBuilder filter(final InputStream inputStream) {
252        this.inputStream = inputStream;
253        return this;
254    }
255
256    /**
257     * Configures an {@link OutputStream} to be written to in addition to the underlying Logger. If no OutputStream is
258     * specified, then the built OutputStream or PrintStream will only write to the underlying Logger.
259     *
260     * @param outputStream the OutputStream to write to in addition to the Logger
261     * @return {@code this}
262     */
263    public IoBuilder filter(final OutputStream outputStream) {
264        this.outputStream = outputStream;
265        return this;
266    }
267
268    // TODO: could this builder use generics to infer the desired IO class?
269
270    /**
271     * Builds a new {@link Reader} that is wiretapped by its underlying Logger. If buffering is enabled, then a
272     * {@link java.io.BufferedReader} will be returned.
273     *
274     * @return a new Reader wiretapped by a Logger
275     * @throws IllegalStateException if no Reader was configured for this builder
276     */
277    public Reader buildReader() {
278        final Reader in = Objects.requireNonNull(this.reader, "reader");
279        if (this.buffered) {
280            if (this.bufferSize > 0) {
281                return new LoggerBufferedReader(in, this.bufferSize, this.logger, this.fqcn, this.level, this.marker);
282            }
283            return new LoggerBufferedReader(in, this.logger, this.fqcn, this.level, this.marker);
284        }
285        return new LoggerReader(in, this.logger, this.fqcn, this.level, this.marker);
286    }
287
288    /**
289     * Builds a new {@link Writer} that is backed by a Logger and optionally writes to another Writer as well. If no
290     * Writer is configured for this builder, then the returned Writer will only write to its underlying Logger.
291     *
292     * @return a new Writer or {@link java.io.FilterWriter} backed by a Logger
293     */
294    public Writer buildWriter() {
295        if (this.writer == null) {
296            return new LoggerWriter(this.logger, this.fqcn, this.level, this.marker);
297        }
298        return new LoggerFilterWriter(this.writer, this.logger, this.fqcn, this.level, this.marker);
299    }
300
301    /**
302     * Builds a new {@link PrintWriter} that is backed by a Logger and optionally writes to another Writer as well. If
303     * no Writer is configured for this builder, then the returned PrintWriter will only write to its underlying
304     * Logger.
305     *
306     * @return a new PrintWriter that optionally writes to another Writer in addition to its underlying Logger
307     */
308    public PrintWriter buildPrintWriter() {
309        if (this.writer == null) {
310            return new LoggerPrintWriter(this.logger, this.autoFlush, this.fqcn, this.level, this.marker);
311        }
312        return new LoggerPrintWriter(this.writer, this.autoFlush, this.logger, this.fqcn, this.level, this.marker);
313    }
314
315    /**
316     * Builds a new {@link InputStream} that is wiretapped by its underlying Logger. If buffering is enabled, then a
317     * {@link java.io.BufferedInputStream} will be returned.
318     *
319     * @return a new InputStream wiretapped by a Logger
320     * @throws IllegalStateException if no InputStream was configured for this builder
321     */
322    public InputStream buildInputStream() {
323        final InputStream in = Objects.requireNonNull(this.inputStream, "inputStream");
324        if (this.buffered) {
325            if (this.bufferSize > 0) {
326                return new LoggerBufferedInputStream(in, this.charset, this.bufferSize, this.logger, this.fqcn,
327                    this.level, this.marker);
328            }
329            return new LoggerBufferedInputStream(in, this.charset, this.logger, this.fqcn, this.level, this.marker);
330        }
331        return new LoggerInputStream(in, this.charset, this.logger, this.fqcn, this.level, this.marker);
332    }
333
334    /**
335     * Builds a new {@link OutputStream} that is backed by a Logger and optionally writes to another OutputStream as
336     * well. If no OutputStream is configured for this builder, then the returned OutputStream will only write to its
337     * underlying Logger.
338     *
339     * @return a new OutputStream that optionally writes to another OutputStream in addition to its underlying Logger
340     */
341    public OutputStream buildOutputStream() {
342        if (this.outputStream == null) {
343            return new LoggerOutputStream(this.logger, this.level, this.marker, this.charset, this.fqcn);
344        }
345        return new LoggerFilterOutputStream(this.outputStream, this.charset, this.logger, this.fqcn, this.level,
346            this.marker);
347    }
348
349    /**
350     * Builds a new {@link PrintStream} that is backed by a Logger and optionally writes to another OutputStream as
351     * well. If no OutputStream is configured for this builder, then the returned PrintStream will only write to its
352     * underlying Logger.
353     *
354     * @return a new PrintStream that optionally writes to another OutputStream in addition to its underlying Logger
355     * @throws LoggingException if the configured character set is unsupported by {@link PrintStream}
356     */
357    public PrintStream buildPrintStream() {
358        try {
359            if (this.outputStream == null) {
360                return new LoggerPrintStream(this.logger, this.autoFlush, this.charset, this.fqcn, this.level,
361                    this.marker);
362            }
363            return new LoggerPrintStream(this.outputStream, this.autoFlush, this.charset, this.logger, this.fqcn,
364                this.level, this.marker);
365        } catch (final UnsupportedEncodingException e) {
366            // this exception shouldn't really happen since we use Charset and not String
367            throw new LoggingException(e);
368        }
369    }
370
371}