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 */ 017 018package org.apache.log4j; 019 020import org.apache.log4j.helpers.QuietWriter; 021import org.apache.log4j.spi.ErrorHandler; 022import org.apache.log4j.spi.LoggingEvent; 023import org.apache.logging.log4j.status.StatusLogger; 024 025import java.io.IOException; 026import java.io.InterruptedIOException; 027import java.io.OutputStream; 028import java.io.OutputStreamWriter; 029import java.io.Writer; 030 031 032/** 033 * WriterAppender appends log events to a {@link Writer} or an 034 * {@link OutputStream} depending on the user's choice. 035 */ 036public class WriterAppender extends AppenderSkeleton { 037 private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); 038 039 /** 040 * Immediate flush means that the underlying writer or output stream 041 * will be flushed at the end of each append operation unless shouldFlush() 042 * is overridden. Immediate 043 * flush is slower but ensures that each append request is actually 044 * written. If <code>immediateFlush</code> is set to 045 * <code>false</code>, then there is a good chance that the last few 046 * logs events are not actually written to persistent media if and 047 * when the application crashes. 048 * 049 * <p>The <code>immediateFlush</code> variable is set to 050 * <code>true</code> by default. 051 */ 052 protected boolean immediateFlush = true; 053 054 /** 055 * The encoding to use when writing. <p>The 056 * <code>encoding</code> variable is set to <code>null</null> by 057 * default which results in the utilization of the system's default 058 * encoding. 059 */ 060 protected String encoding; 061 062 /** 063 * This is the {@link QuietWriter quietWriter} where we will write 064 * to. 065 */ 066 protected QuietWriter qw; 067 068 069 /** 070 * This default constructor does nothing. 071 */ 072 public WriterAppender() { 073 } 074 075 /** 076 * Instantiate a WriterAppender and set the output destination to a 077 * new {@link OutputStreamWriter} initialized with <code>os</code> 078 * as its {@link OutputStream}. 079 */ 080 public WriterAppender(Layout layout, OutputStream os) { 081 this(layout, new OutputStreamWriter(os)); 082 } 083 084 /** 085 * Instantiate a WriterAppender and set the output destination to 086 * <code>writer</code>. 087 * 088 * <p>The <code>writer</code> must have been previously opened by 089 * the user. 090 */ 091 public WriterAppender(Layout layout, Writer writer) { 092 this.layout = layout; 093 this.setWriter(writer); 094 } 095 096 /** 097 * Returns value of the <b>ImmediateFlush</b> option. 098 * @return the value of the immediate flush setting. 099 */ 100 public boolean getImmediateFlush() { 101 return immediateFlush; 102 } 103 104 /** 105 * If the <b>ImmediateFlush</b> option is set to 106 * <code>true</code>, the appender will flush at the end of each 107 * write. This is the default behavior. If the option is set to 108 * <code>false</code>, then the underlying stream can defer writing 109 * to physical medium to a later time. 110 * 111 * <p>Avoiding the flush operation at the end of each append results in 112 * a performance gain of 10 to 20 percent. However, there is safety 113 * tradeoff involved in skipping flushing. Indeed, when flushing is 114 * skipped, then it is likely that the last few log events will not 115 * be recorded on disk when the application exits. This is a high 116 * price to pay even for a 20% performance gain. 117 * 118 * @param value the value to set the immediate flush setting to. 119 */ 120 public void setImmediateFlush(boolean value) { 121 immediateFlush = value; 122 } 123 124 /** 125 * Does nothing. 126 */ 127 @Override 128 public void activateOptions() { 129 } 130 131 132 /** 133 * This method is called by the {@link AppenderSkeleton#doAppend} 134 * method. 135 * 136 * <p>If the output stream exists and is writable then write a log 137 * statement to the output stream. Otherwise, write a single warning 138 * message to <code>System.err</code>. 139 * 140 * <p>The format of the output will depend on this appender's 141 * layout. 142 */ 143 @Override 144 public void append(LoggingEvent event) { 145 146 // Reminder: the nesting of calls is: 147 // 148 // doAppend() 149 // - check threshold 150 // - filter 151 // - append(); 152 // - checkEntryConditions(); 153 // - subAppend(); 154 155 if (!checkEntryConditions()) { 156 return; 157 } 158 subAppend(event); 159 } 160 161 /** 162 * This method determines if there is a sense in attempting to append. 163 * 164 * <p>It checks whether there is a set output target and also if 165 * there is a set layout. If these checks fail, then the boolean 166 * value <code>false</code> is returned. 167 * @return true if appending is allowed, false otherwise. 168 */ 169 protected boolean checkEntryConditions() { 170 if (this.closed) { 171 LOGGER.warn("Not allowed to write to a closed appender."); 172 return false; 173 } 174 175 if (this.qw == null) { 176 errorHandler.error("No output stream or file set for the appender named [" + name + "]."); 177 return false; 178 } 179 180 if (this.layout == null) { 181 errorHandler.error("No layout set for the appender named [" + name + "]."); 182 return false; 183 } 184 return true; 185 } 186 187 188 /** 189 * Close this appender instance. The underlying stream or writer is 190 * also closed. 191 * 192 * <p>Closed appenders cannot be reused. 193 * 194 * @see #setWriter 195 * @since 0.8.4 196 */ 197 @Override 198 public synchronized void close() { 199 if (this.closed) { 200 return; 201 } 202 this.closed = true; 203 writeFooter(); 204 reset(); 205 } 206 207 /** 208 * Close the underlying {@link Writer}. 209 */ 210 protected void closeWriter() { 211 if (qw != null) { 212 try { 213 qw.close(); 214 } catch (IOException e) { 215 if (e instanceof InterruptedIOException) { 216 Thread.currentThread().interrupt(); 217 } 218 // There is do need to invoke an error handler at this late 219 // stage. 220 LOGGER.error("Could not close " + qw, e); 221 } 222 } 223 } 224 225 /** 226 * Returns an OutputStreamWriter when passed an OutputStream. The 227 * encoding used will depend on the value of the 228 * <code>encoding</code> property. If the encoding value is 229 * specified incorrectly the writer will be opened using the default 230 * system encoding (an error message will be printed to the LOGGER. 231 * @param os The OutputStream. 232 * @return The OutputStreamWriter. 233 */ 234 protected OutputStreamWriter createWriter(OutputStream os) { 235 OutputStreamWriter retval = null; 236 237 String enc = getEncoding(); 238 if (enc != null) { 239 try { 240 retval = new OutputStreamWriter(os, enc); 241 } catch (IOException e) { 242 if (e instanceof InterruptedIOException) { 243 Thread.currentThread().interrupt(); 244 } 245 LOGGER.warn("Error initializing output writer."); 246 LOGGER.warn("Unsupported encoding?"); 247 } 248 } 249 if (retval == null) { 250 retval = new OutputStreamWriter(os); 251 } 252 return retval; 253 } 254 255 public String getEncoding() { 256 return encoding; 257 } 258 259 public void setEncoding(String value) { 260 encoding = value; 261 } 262 263 264 /** 265 * Set the {@link ErrorHandler} for this WriterAppender and also the 266 * underlying {@link QuietWriter} if any. 267 */ 268 @Override 269 public synchronized void setErrorHandler(ErrorHandler eh) { 270 if (eh == null) { 271 LOGGER.warn("You have tried to set a null error-handler."); 272 } else { 273 this.errorHandler = eh; 274 if (this.qw != null) { 275 this.qw.setErrorHandler(eh); 276 } 277 } 278 } 279 280 /** 281 * <p>Sets the Writer where the log output will go. The 282 * specified Writer must be opened by the user and be 283 * writable. 284 * 285 * <p>The <code>java.io.Writer</code> will be closed when the 286 * appender instance is closed. 287 * 288 * 289 * <p><b>WARNING:</b> Logging to an unopened Writer will fail. 290 * <p> 291 * 292 * @param writer An already opened Writer. 293 */ 294 public synchronized void setWriter(Writer writer) { 295 reset(); 296 this.qw = new QuietWriter(writer, errorHandler); 297 //this.tp = new TracerPrintWriter(qw); 298 writeHeader(); 299 } 300 301 302 /** 303 * Actual writing occurs here. 304 * 305 * <p>Most subclasses of <code>WriterAppender</code> will need to 306 * override this method. 307 * @param event The event to log. 308 * 309 * @since 0.9.0 310 */ 311 protected void subAppend(LoggingEvent event) { 312 this.qw.write(this.layout.format(event)); 313 314 if (layout.ignoresThrowable()) { 315 String[] s = event.getThrowableStrRep(); 316 if (s != null) { 317 int len = s.length; 318 for (int i = 0; i < len; i++) { 319 this.qw.write(s[i]); 320 this.qw.write(Layout.LINE_SEP); 321 } 322 } 323 } 324 325 if (shouldFlush(event)) { 326 this.qw.flush(); 327 } 328 } 329 330 331 /** 332 * The WriterAppender requires a layout. Hence, this method returns 333 * <code>true</code>. 334 */ 335 @Override 336 public boolean requiresLayout() { 337 return true; 338 } 339 340 /** 341 * Clear internal references to the writer and other variables. 342 * <p> 343 * Subclasses can override this method for an alternate closing 344 * behavior. 345 */ 346 protected void reset() { 347 closeWriter(); 348 this.qw = null; 349 //this.tp = null; 350 } 351 352 353 /** 354 * Write a footer as produced by the embedded layout's {@link 355 * Layout#getFooter} method. 356 */ 357 protected void writeFooter() { 358 if (layout != null) { 359 String f = layout.getFooter(); 360 if (f != null && this.qw != null) { 361 this.qw.write(f); 362 this.qw.flush(); 363 } 364 } 365 } 366 367 /** 368 * Write a header as produced by the embedded layout's {@link 369 * Layout#getHeader} method. 370 */ 371 protected void writeHeader() { 372 if (layout != null) { 373 String h = layout.getHeader(); 374 if (h != null && this.qw != null) { 375 this.qw.write(h); 376 } 377 } 378 } 379 380 /** 381 * Determines whether the writer should be flushed after 382 * this event is written. 383 * @param event The event to log. 384 * @return true if the writer should be flushed. 385 * 386 * @since 1.2.16 387 */ 388 protected boolean shouldFlush(final LoggingEvent event) { 389 return immediateFlush; 390 } 391}