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 }