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 }