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