001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.io; 018 019import java.io.InputStream; 020import java.io.OutputStream; 021import java.io.PrintStream; 022import java.io.PrintWriter; 023import java.io.Reader; 024import java.io.UnsupportedEncodingException; 025import java.io.Writer; 026import java.nio.charset.Charset; 027import java.util.Objects; 028 029import org.apache.logging.log4j.Level; 030import org.apache.logging.log4j.LogManager; 031import org.apache.logging.log4j.Logger; 032import org.apache.logging.log4j.LoggingException; 033import org.apache.logging.log4j.Marker; 034import org.apache.logging.log4j.spi.ExtendedLogger; 035import org.apache.logging.log4j.util.StackLocatorUtil; 036 037/** 038 * Builder class to wrap {@link Logger Loggers} into Java IO compatible classes. 039 * 040 * <p>Both the {@link InputStream}/{@link OutputStream} and {@link Reader}/{@link Writer} family of classes are 041 * supported. {@link OutputStream} and {@link Writer} instances can be wrapped by a filtered version of their 042 * corresponding classes ({@link java.io.FilterOutputStream} and {@link java.io.FilterWriter}) in order to log all 043 * lines written to these instances. {@link InputStream} and {@link Reader} instances can be wrapped by a sort of 044 * wiretapped version of their respective classes; all lines read from these instances will be logged.</p> 045 * 046 * <p>The main feature, however, is the ability to create a {@link PrintWriter}, {@link PrintStream}, {@link Writer}, 047 * {@link java.io.BufferedWriter}, {@link OutputStream}, or {@link java.io.BufferedOutputStream} that is backed by a 048 * {@link Logger}. The main inspiration for this feature is the JDBC API which uses a PrintWriter to perform debug 049 * logging. In order to properly integrate APIs like JDBC into Log4j, create a PrintWriter using this class.</p> 050 * 051 * <p>The IoBuilder support configuration of the logging {@link Level} it should use (defaults to the level of 052 * the underlying Logger), and an optional {@link Marker}. The other configurable objects are explained in more 053 * detail below.</p> 054 * 055 * @since 2.1 056 */ 057public class IoBuilder { 058 private final ExtendedLogger logger; 059 private Level level; 060 private Marker marker; 061 private String fqcn; 062 private boolean autoFlush; 063 private boolean buffered; 064 private int bufferSize; 065 private Charset charset; 066 private Reader reader; 067 private Writer writer; 068 private InputStream inputStream; 069 private OutputStream outputStream; 070 071 /** 072 * Creates a new builder for a given {@link Logger}. The Logger instance must implement {@link ExtendedLogger} or 073 * an exception will be thrown. 074 * 075 * @param logger the Logger to wrap into a LoggerStream 076 * @return a new IoBuilder 077 * @throws UnsupportedOperationException if {@code logger} does not implement {@link ExtendedLogger} or if 078 * {@code logger} is {@code null} 079 */ 080 public static IoBuilder forLogger(final Logger logger) { 081 return new IoBuilder(logger); 082 } 083 084 /** 085 * Creates a new builder using a Logger name. The name provided is used to get a Logger from 086 * {@link LogManager#getLogger(String)} which will be wrapped into a LoggerStream. 087 * 088 * @param loggerName the name of the Logger to wrap into a LoggerStream 089 * @return a new IoBuilder 090 */ 091 public static IoBuilder forLogger(final String loggerName) { 092 return new IoBuilder(LogManager.getLogger(loggerName)); 093 } 094 095 /** 096 * Creates a new builder using a Logger named after a given Class. The Class provided is used to get a Logger from 097 * {@link LogManager#getLogger(Class)} which will be wrapped into a LoggerStream. 098 * 099 * @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}