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  import java.util.Objects;
28  
29  import org.apache.logging.log4j.Level;
30  import org.apache.logging.log4j.LogManager;
31  import org.apache.logging.log4j.Logger;
32  import org.apache.logging.log4j.LoggingException;
33  import org.apache.logging.log4j.Marker;
34  import org.apache.logging.log4j.spi.ExtendedLogger;
35  import org.apache.logging.log4j.util.StackLocatorUtil;
36  
37  /**
38   * Builder class to wrap {@link Logger Loggers} into Java IO compatible classes.
39   *
40   * <p>Both the {@link InputStream}/{@link OutputStream} and {@link Reader}/{@link Writer} family of classes are
41   * supported. {@link OutputStream} and {@link Writer} instances can be wrapped by a filtered version of their
42   * corresponding classes ({@link java.io.FilterOutputStream} and {@link java.io.FilterWriter}) in order to log all
43   * lines written to these instances. {@link InputStream} and {@link Reader} instances can be wrapped by a sort of
44   * wiretapped version of their respective classes; all lines read from these instances will be logged.</p>
45   *
46   * <p>The main feature, however, is the ability to create a {@link PrintWriter}, {@link PrintStream}, {@link Writer},
47   * {@link java.io.BufferedWriter}, {@link OutputStream}, or {@link java.io.BufferedOutputStream} that is backed by a
48   * {@link Logger}. The main inspiration for this feature is the JDBC API which uses a PrintWriter to perform debug
49   * logging. In order to properly integrate APIs like JDBC into Log4j, create a PrintWriter using this class.</p>
50   *
51   * <p>The IoBuilder support configuration of the logging {@link Level} it should use (defaults to the level of
52   * the underlying Logger), and an optional {@link Marker}. The other configurable objects are explained in more
53   * detail below.</p>
54   *
55   * @since 2.1
56   */
57  public class IoBuilder {
58      private final ExtendedLogger logger;
59      private Level level;
60      private Marker marker;
61      private String fqcn;
62      private boolean autoFlush;
63      private boolean buffered;
64      private int bufferSize;
65      private Charset charset;
66      private Reader reader;
67      private Writer writer;
68      private InputStream inputStream;
69      private OutputStream outputStream;
70  
71      /**
72       * Creates a new builder for a given {@link Logger}. The Logger instance must implement {@link ExtendedLogger} or
73       * an exception will be thrown.
74       *
75       * @param logger the Logger to wrap into a LoggerStream
76       * @return a new IoBuilder
77       * @throws UnsupportedOperationException if {@code logger} does not implement {@link ExtendedLogger} or if
78       *                                       {@code logger} is {@code null}
79       */
80      public static IoBuilder forLogger(final Logger logger) {
81          return new IoBuilder(logger);
82      }
83  
84      /**
85       * Creates a new builder using a Logger name. The name provided is used to get a Logger from
86       * {@link LogManager#getLogger(String)} which will be wrapped into a LoggerStream.
87       *
88       * @param loggerName the name of the Logger to wrap into a LoggerStream
89       * @return a new IoBuilder
90       */
91      public static IoBuilder forLogger(final String loggerName) {
92          return new IoBuilder(LogManager.getLogger(loggerName));
93      }
94  
95      /**
96       * Creates a new builder using a Logger named after a given Class. The Class provided is used to get a Logger from
97       * {@link LogManager#getLogger(Class)} which will be wrapped into a LoggerStream.
98       *
99       * @param clazz the Class to use as the Logger name to wrap into a LoggerStream
100      * @return a new IoBuilder
101      */
102     public static IoBuilder forLogger(final Class<?> clazz) {
103         return new IoBuilder(LogManager.getLogger(clazz));
104     }
105 
106     /**
107      * Creates a new builder using a Logger named after the calling Class. This is equivalent to the following:
108      * <pre>
109      *     IoBuilder builder = IoBuilder.forLogger(LogManager.getLogger());
110      * </pre>
111      *
112      * @return a new IoBuilder
113      */
114     public static IoBuilder forLogger() {
115         return new IoBuilder(LogManager.getLogger(StackLocatorUtil.getCallerClass(2)));
116     }
117 
118     /**
119      * Constructs a new IoBuilder for the given Logger. This method is provided for extensibility of this builder
120      * class. The static factory methods should be used normally.
121      *
122      * @param logger the {@link ExtendedLogger} to wrap
123      */
124     protected IoBuilder(final Logger logger) {
125         if (!(logger instanceof ExtendedLogger)) {
126             throw new UnsupportedOperationException("The provided Logger [" + String.valueOf(logger) +
127                 "] does not implement " + ExtendedLogger.class.getName());
128         }
129         this.logger = (ExtendedLogger) logger;
130     }
131 
132     /**
133      * Specifies the {@link Level} to log at. If no Level is configured, then the Level of the wrapped Logger will be
134      * used.
135      *
136      * @param level the Level to use for logging
137      * @return {@code this}
138      */
139     public IoBuilder setLevel(final Level level) {
140         this.level = level;
141         return this;
142     }
143 
144     /**
145      * Specifies an optional {@link Marker} to use in all logging messages. If no Marker is specified, then no Marker
146      * will be used.
147      *
148      * @param marker the Marker to associate with all logging messages
149      * @return {@code this}
150      */
151     public IoBuilder setMarker(final Marker marker) {
152         this.marker = marker;
153         return this;
154     }
155 
156     /**
157      * Specifies the fully qualified class name of the IO wrapper class implementation. This method should only be
158      * used when making significant extensions to the provided classes in this component and is normally unnecessary.
159      *
160      * @param fqcn the fully qualified class name of the IO wrapper class being built
161      * @return {@code this}
162      */
163     public IoBuilder setWrapperClassName(final String fqcn) {
164         this.fqcn = fqcn;
165         return this;
166     }
167 
168     /**
169      * Indicates whether or not a built {@link PrintWriter} or {@link PrintStream} should automatically flush when
170      * one of the {@code println}, {@code printf}, or {@code format} methods are invoked, or when a new line character
171      * is printed.
172      *
173      * @param autoFlush if {@code true}, then {@code println}, {@code printf}, and {@code format} will auto flush
174      * @return {@code this}
175      */
176     public IoBuilder setAutoFlush(final boolean autoFlush) {
177         this.autoFlush = autoFlush;
178         return this;
179     }
180 
181     /**
182      * Enables or disables using a buffered variant of the desired IO class. If this is set to {@code true}, then the
183      * instances returned by {@link #buildReader()} and {@link #buildInputStream()} can be safely cast (if necessary)
184      * to {@link java.io.BufferedReader} and {@link java.io.BufferedInputStream} respectively. This option does not
185      * have any effect on the other built variants.
186      *
187      * @param buffered indicates whether or not a wrapped {@link InputStream} or {@link Reader} should be buffered
188      * @return {@code this}
189      */
190     public IoBuilder setBuffered(final boolean buffered) {
191         this.buffered = buffered;
192         return this;
193     }
194 
195     /**
196      * Configures the buffer size to use when building a {@link java.io.BufferedReader} or
197      * {@link java.io.BufferedInputStream} LoggerStream.
198      *
199      * @param bufferSize the buffer size to use or a non-positive integer to use the default size
200      * @return {@code this}
201      */
202     public IoBuilder setBufferSize(final int bufferSize) {
203         this.bufferSize = bufferSize;
204         return this;
205     }
206 
207     /**
208      * Specifies the character set to use when building an {@link InputStream}, {@link OutputStream}, or
209      * {@link PrintStream}. If no character set is specified, then {@link java.nio.charset.Charset#defaultCharset()}
210      * is used.
211      *
212      * @param charset the character set to use when building an InputStream, OutputStream, or PrintStream
213      * @return {@code this}
214      */
215     public IoBuilder setCharset(final Charset charset) {
216         this.charset = charset;
217         return this;
218     }
219 
220     /**
221      * Configures a {@link Reader} to be wiretapped when building a Reader. This must be set to a non-{@code null}
222      * value in order to call {@link #buildReader()}.
223      *
224      * @param reader the Reader to wiretap
225      * @return {@code this}
226      */
227     public IoBuilder filter(final Reader reader) {
228         this.reader = reader;
229         return this;
230     }
231 
232     /**
233      * Configures a {@link Writer} to be written to in addition to the underlying Logger. If no Writer is specified,
234      * then the built Writer or PrintWriter will only write to the underlying Logger.
235      *
236      * @param writer the Writer to write to in addition to the Logger
237      * @return {@code this}
238      */
239     public IoBuilder filter(final Writer writer) {
240         this.writer = writer;
241         return this;
242     }
243 
244     /**
245      * Configures an {@link InputStream} to be wiretapped when building an InputStream. This must be set to a
246      * non-{@code null} value in order to call {@link #buildInputStream()}.
247      *
248      * @param inputStream the InputStream to wiretap
249      * @return {@code this}
250      */
251     public IoBuilder filter(final InputStream inputStream) {
252         this.inputStream = inputStream;
253         return this;
254     }
255 
256     /**
257      * Configures an {@link OutputStream} to be written to in addition to the underlying Logger. If no OutputStream is
258      * specified, then the built OutputStream or PrintStream will only write to the underlying Logger.
259      *
260      * @param outputStream the OutputStream to write to in addition to the Logger
261      * @return {@code this}
262      */
263     public IoBuilder filter(final OutputStream outputStream) {
264         this.outputStream = outputStream;
265         return this;
266     }
267 
268     // TODO: could this builder use generics to infer the desired IO class?
269 
270     /**
271      * Builds a new {@link Reader} that is wiretapped by its underlying Logger. If buffering is enabled, then a
272      * {@link java.io.BufferedReader} will be returned.
273      *
274      * @return a new Reader wiretapped by a Logger
275      * @throws IllegalStateException if no Reader was configured for this builder
276      */
277     public Reader buildReader() {
278         final Reader in = Objects.requireNonNull(this.reader, "reader");
279         if (this.buffered) {
280             if (this.bufferSize > 0) {
281                 return new LoggerBufferedReader(in, this.bufferSize, this.logger, this.fqcn, this.level, this.marker);
282             }
283             return new LoggerBufferedReader(in, this.logger, this.fqcn, this.level, this.marker);
284         }
285         return new LoggerReader(in, this.logger, this.fqcn, this.level, this.marker);
286     }
287 
288     /**
289      * Builds a new {@link Writer} that is backed by a Logger and optionally writes to another Writer as well. If no
290      * Writer is configured for this builder, then the returned Writer will only write to its underlying Logger.
291      *
292      * @return a new Writer or {@link java.io.FilterWriter} backed by a Logger
293      */
294     public Writer buildWriter() {
295         if (this.writer == null) {
296             return new LoggerWriter(this.logger, this.fqcn, this.level, this.marker);
297         }
298         return new LoggerFilterWriter(this.writer, this.logger, this.fqcn, this.level, this.marker);
299     }
300 
301     /**
302      * Builds a new {@link PrintWriter} that is backed by a Logger and optionally writes to another Writer as well. If
303      * no Writer is configured for this builder, then the returned PrintWriter will only write to its underlying
304      * Logger.
305      *
306      * @return a new PrintWriter that optionally writes to another Writer in addition to its underlying Logger
307      */
308     public PrintWriter buildPrintWriter() {
309         if (this.writer == null) {
310             return new LoggerPrintWriter(this.logger, this.autoFlush, this.fqcn, this.level, this.marker);
311         }
312         return new LoggerPrintWriter(this.writer, this.autoFlush, this.logger, this.fqcn, this.level, this.marker);
313     }
314 
315     /**
316      * Builds a new {@link InputStream} that is wiretapped by its underlying Logger. If buffering is enabled, then a
317      * {@link java.io.BufferedInputStream} will be returned.
318      *
319      * @return a new InputStream wiretapped by a Logger
320      * @throws IllegalStateException if no InputStream was configured for this builder
321      */
322     public InputStream buildInputStream() {
323         final InputStream in = Objects.requireNonNull(this.inputStream, "inputStream");
324         if (this.buffered) {
325             if (this.bufferSize > 0) {
326                 return new LoggerBufferedInputStream(in, this.charset, this.bufferSize, this.logger, this.fqcn,
327                     this.level, this.marker);
328             }
329             return new LoggerBufferedInputStream(in, this.charset, this.logger, this.fqcn, this.level, this.marker);
330         }
331         return new LoggerInputStream(in, this.charset, this.logger, this.fqcn, this.level, this.marker);
332     }
333 
334     /**
335      * Builds a new {@link OutputStream} that is backed by a Logger and optionally writes to another OutputStream as
336      * well. If no OutputStream is configured for this builder, then the returned OutputStream will only write to its
337      * underlying Logger.
338      *
339      * @return a new OutputStream that optionally writes to another OutputStream in addition to its underlying Logger
340      */
341     public OutputStream buildOutputStream() {
342         if (this.outputStream == null) {
343             return new LoggerOutputStream(this.logger, this.level, this.marker, this.charset, this.fqcn);
344         }
345         return new LoggerFilterOutputStream(this.outputStream, this.charset, this.logger, this.fqcn, this.level,
346             this.marker);
347     }
348 
349     /**
350      * Builds a new {@link PrintStream} that is backed by a Logger and optionally writes to another OutputStream as
351      * well. If no OutputStream is configured for this builder, then the returned PrintStream will only write to its
352      * underlying Logger.
353      *
354      * @return a new PrintStream that optionally writes to another OutputStream in addition to its underlying Logger
355      * @throws LoggingException if the configured character set is unsupported by {@link PrintStream}
356      */
357     public PrintStream buildPrintStream() {
358         try {
359             if (this.outputStream == null) {
360                 return new LoggerPrintStream(this.logger, this.autoFlush, this.charset, this.fqcn, this.level,
361                     this.marker);
362             }
363             return new LoggerPrintStream(this.outputStream, this.autoFlush, this.charset, this.logger, this.fqcn,
364                 this.level, this.marker);
365         } catch (final UnsupportedEncodingException e) {
366             // this exception shouldn't really happen since we use Charset and not String
367             throw new LoggingException(e);
368         }
369     }
370 
371 }