View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.io;
    18
    19  import java.io.InputStream;
    20  import java.io.OutputStream;
    21  import java.io.PrintStream;
    22  import java.io.PrintWriter;
    23  import java.io.Reader;
    24  import java.io.UnsupportedEncodingException;
    25  import java.io.Writer;
    26  import java.nio.charset.Charset;
    27
    28  import org.apache.logging.log4j.Level;
    29  import org.apache.logging.log4j.LogManager;
    30  import org.apache.logging.log4j.Logger;
    31  import org.apache.logging.log4j.LoggingException;
    32  import org.apache.logging.log4j.Marker;
    33  import org.apache.logging.log4j.spi.ExtendedLogger;
    34  import org.apache.logging.log4j.util.ReflectionUtil;
    35
    36  /**
    37   * Builder class to wrap {@link Logger Loggers} into Java IO compatible classes.
    38   *
    39   * <p>Both the {@link InputStream}/{@link OutputStream} and {@link Reader}/{@link Writer} family of classes are
    40   * supported. {@link OutputStream} and {@link Writer} instances can be wrapped by a filtered version of their
    41   * corresponding classes ({@link java.io.FilterOutputStream} and {@link java.io.FilterWriter}) in order to log all
    42   * lines written to these instances. {@link InputStream} and {@link Reader} instances can be wrapped by a sort of
    43   * wiretapped version of their respective classes; all lines read from these instances will be logged.</p>
    44   *
    45   * <p>The main feature, however, is the ability to create a {@link PrintWriter}, {@link PrintStream}, {@link Writer},
    46   * {@link java.io.BufferedWriter}, {@link OutputStream}, or {@link java.io.BufferedOutputStream} that is backed by a
    47   * {@link Logger}. The main inspiration for this feature is the JDBC API which uses a PrintWriter to perform debug
    48   * logging. In order to properly integrate APIs like JDBC into Log4j, create a PrintWriter using this class.</p>
    49   *
    50   * <p>The IoBuilder support configuration of the logging {@link Level} it should use (defaults to the level of
    51   * the underlying Logger), and an optional {@link Marker}. The other configurable objects are explained in more
    52   * detail below.</p>
    53   *
    54   * @since 2.1
    55   */
    56  public class IoBuilder {
    57      private final ExtendedLogger logger;
    58      private Level level;
    59      private Marker marker;
    60      private String fqcn;
    61      private boolean autoFlush;
    62      private boolean buffered;
    63      private int bufferSize;
    64      private Charset charset;
    65      private Reader reader;
    66      private Writer writer;
    67      private InputStream inputStream;
    68      private OutputStream outputStream;
    69
    70      /**
    71       * Creates a new builder for a given {@link Logger}. The Logger instance must implement {@link ExtendedLogger} or
    72       * an exception will be thrown.
    73       *
    74       * @param logger the Logger to wrap into a LoggerStream
    75       * @return a new IoBuilder
    76       * @throws UnsupportedOperationException if {@code logger} does not implement {@link ExtendedLogger} or if
    77       *                                       {@code logger} is {@code null}
    78       */
    79      public static IoBuilder forLogger(final Logger logger) {
    80          return new IoBuilder(logger);
    81      }
    82
    83      /**
    84       * Creates a new builder using a Logger name. The name provided is used to get a Logger from
    85       * {@link LogManager#getLogger(String)} which will be wrapped into a LoggerStream.
    86       *
    87       * @param loggerName the name of the Logger to wrap into a LoggerStream
    88       * @return a new IoBuilder
    89       */
    90      public static IoBuilder forLogger(final String loggerName) {
    91          return new IoBuilder(LogManager.getLogger(loggerName));
    92      }
    93
    94      /**
    95       * Creates a new builder using a Logger named after a given Class. The Class provided is used to get a Logger from
    96       * {@link LogManager#getLogger(Class)} which will be wrapped into a LoggerStream.
    97       *
    98       * @param clazz the Class to use as the Logger name to wrap into a LoggerStream
    99       * @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 }