    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    }