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 }