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; 027 028import org.apache.logging.log4j.Level; 029import org.apache.logging.log4j.LogManager; 030import org.apache.logging.log4j.Logger; 031import org.apache.logging.log4j.LoggingException; 032import org.apache.logging.log4j.Marker; 033import org.apache.logging.log4j.spi.ExtendedLogger; 034import org.apache.logging.log4j.util.ReflectionUtil; 035 036/** 037 * Builder class to wrap {@link Logger Loggers} into Java IO compatible classes. 038 * 039 * <p>Both the {@link InputStream}/{@link OutputStream} and {@link Reader}/{@link Writer} family of classes are 040 * supported. {@link OutputStream} and {@link Writer} instances can be wrapped by a filtered version of their 041 * corresponding classes ({@link java.io.FilterOutputStream} and {@link java.io.FilterWriter}) in order to log all 042 * lines written to these instances. {@link InputStream} and {@link Reader} instances can be wrapped by a sort of 043 * wiretapped version of their respective classes; all lines read from these instances will be logged.</p> 044 * 045 * <p>The main feature, however, is the ability to create a {@link PrintWriter}, {@link PrintStream}, {@link Writer}, 046 * {@link java.io.BufferedWriter}, {@link OutputStream}, or {@link java.io.BufferedOutputStream} that is backed by a 047 * {@link Logger}. The main inspiration for this feature is the JDBC API which uses a PrintWriter to perform debug 048 * logging. In order to properly integrate APIs like JDBC into Log4j, create a PrintWriter using this class.</p> 049 * 050 * <p>The IoBuilder support configuration of the logging {@link Level} it should use (defaults to the level of 051 * the underlying Logger), and an optional {@link Marker}. The other configurable objects are explained in more 052 * detail below.</p> 053 * 054 * @since 2.1 055 */ 056public class IoBuilder { 057 private final ExtendedLogger logger; 058 private Level level; 059 private Marker marker; 060 private String fqcn; 061 private boolean autoFlush; 062 private boolean buffered; 063 private int bufferSize; 064 private Charset charset; 065 private Reader reader; 066 private Writer writer; 067 private InputStream inputStream; 068 private OutputStream outputStream; 069 070 /** 071 * Creates a new builder for a given {@link Logger}. The Logger instance must implement {@link ExtendedLogger} or 072 * an exception will be thrown. 073 * 074 * @param logger the Logger to wrap into a LoggerStream 075 * @return a new IoBuilder 076 * @throws UnsupportedOperationException if {@code logger} does not implement {@link ExtendedLogger} or if 077 * {@code logger} is {@code null} 078 */ 079 public static IoBuilder forLogger(final Logger logger) { 080 return new IoBuilder(logger); 081 } 082 083 /** 084 * Creates a new builder using a Logger name. The name provided is used to get a Logger from 085 * {@link LogManager#getLogger(String)} which will be wrapped into a LoggerStream. 086 * 087 * @param loggerName the name of the Logger to wrap into a LoggerStream 088 * @return a new IoBuilder 089 */ 090 public static IoBuilder forLogger(final String loggerName) { 091 return new IoBuilder(LogManager.getLogger(loggerName)); 092 } 093 094 /** 095 * Creates a new builder using a Logger named after a given Class. The Class provided is used to get a Logger from 096 * {@link LogManager#getLogger(Class)} which will be wrapped into a LoggerStream. 097 * 098 * @param clazz the Class to use as the Logger name to wrap into a LoggerStream 099 * @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}