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    package org.apache.logging.log4j.io;
018    
019    import java.io.InputStream;
020    import java.io.OutputStream;
021    import java.io.PrintStream;
022    import java.io.PrintWriter;
023    import java.io.Reader;
024    import java.io.UnsupportedEncodingException;
025    import java.io.Writer;
026    import java.nio.charset.Charset;
027    
028    import org.apache.logging.log4j.Level;
029    import org.apache.logging.log4j.LogManager;
030    import org.apache.logging.log4j.Logger;
031    import org.apache.logging.log4j.LoggingException;
032    import org.apache.logging.log4j.Marker;
033    import org.apache.logging.log4j.spi.ExtendedLogger;
034    import 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     */
056    public 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    }