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 18 package org.apache.log4j; 19 20 import org.apache.log4j.helpers.QuietWriter; 21 import org.apache.log4j.spi.ErrorHandler; 22 import org.apache.log4j.spi.LoggingEvent; 23 import org.apache.logging.log4j.status.StatusLogger; 24 25 import java.io.IOException; 26 import java.io.InterruptedIOException; 27 import java.io.OutputStream; 28 import java.io.OutputStreamWriter; 29 import java.io.Writer; 30 31 32 /** 33 * WriterAppender appends log events to a {@link Writer} or an 34 * {@link OutputStream} depending on the user's choice. 35 */ 36 public class WriterAppender extends AppenderSkeleton { 37 private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); 38 39 /** 40 * Immediate flush means that the underlying writer or output stream 41 * will be flushed at the end of each append operation unless shouldFlush() 42 * is overridden. Immediate 43 * flush is slower but ensures that each append request is actually 44 * written. If <code>immediateFlush</code> is set to 45 * <code>false</code>, then there is a good chance that the last few 46 * logs events are not actually written to persistent media if and 47 * when the application crashes. 48 * 49 * <p>The <code>immediateFlush</code> variable is set to 50 * <code>true</code> by default. 51 */ 52 protected boolean immediateFlush = true; 53 54 /** 55 * The encoding to use when writing. <p>The 56 * <code>encoding</code> variable is set to <code>null</null> by 57 * default which results in the utilization of the system's default 58 * encoding. 59 */ 60 protected String encoding; 61 62 /** 63 * This is the {@link QuietWriter quietWriter} where we will write 64 * to. 65 */ 66 protected QuietWriter qw; 67 68 69 /** 70 * This default constructor does nothing. 71 */ 72 public WriterAppender() { 73 } 74 75 /** 76 * Instantiate a WriterAppender and set the output destination to a 77 * new {@link OutputStreamWriter} initialized with <code>os</code> 78 * as its {@link OutputStream}. 79 */ 80 public WriterAppender(Layout layout, OutputStream os) { 81 this(layout, new OutputStreamWriter(os)); 82 } 83 84 /** 85 * Instantiate a WriterAppender and set the output destination to 86 * <code>writer</code>. 87 * 88 * <p>The <code>writer</code> must have been previously opened by 89 * the user. 90 */ 91 public WriterAppender(Layout layout, Writer writer) { 92 this.layout = layout; 93 this.setWriter(writer); 94 } 95 96 /** 97 * Returns value of the <b>ImmediateFlush</b> option. 98 * @return the value of the immediate flush setting. 99 */ 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 public void activateOptions() { 128 } 129 130 131 /** 132 * This method is called by the {@link AppenderSkeleton#doAppend} 133 * method. 134 * 135 * <p>If the output stream exists and is writable then write a log 136 * statement to the output stream. Otherwise, write a single warning 137 * message to <code>System.err</code>. 138 * 139 * <p>The format of the output will depend on this appender's 140 * layout. 141 */ 142 public void append(LoggingEvent event) { 143 144 // Reminder: the nesting of calls is: 145 // 146 // doAppend() 147 // - check threshold 148 // - filter 149 // - append(); 150 // - checkEntryConditions(); 151 // - subAppend(); 152 153 if (!checkEntryConditions()) { 154 return; 155 } 156 subAppend(event); 157 } 158 159 /** 160 * This method determines if there is a sense in attempting to append. 161 * 162 * <p>It checks whether there is a set output target and also if 163 * there is a set layout. If these checks fail, then the boolean 164 * value <code>false</code> is returned. 165 * @return true if appending is allowed, false otherwise. 166 */ 167 protected boolean checkEntryConditions() { 168 if (this.closed) { 169 LOGGER.warn("Not allowed to write to a closed appender."); 170 return false; 171 } 172 173 if (this.qw == null) { 174 errorHandler.error("No output stream or file set for the appender named [" + name + "]."); 175 return false; 176 } 177 178 if (this.layout == null) { 179 errorHandler.error("No layout set for the appender named [" + name + "]."); 180 return false; 181 } 182 return true; 183 } 184 185 186 /** 187 * Close this appender instance. The underlying stream or writer is 188 * also closed. 189 * 190 * <p>Closed appenders cannot be reused. 191 * 192 * @see #setWriter 193 * @since 0.8.4 194 */ 195 public 196 synchronized void close() { 197 if (this.closed) { 198 return; 199 } 200 this.closed = true; 201 writeFooter(); 202 reset(); 203 } 204 205 /** 206 * Close the underlying {@link Writer}. 207 */ 208 protected void closeWriter() { 209 if (qw != null) { 210 try { 211 qw.close(); 212 } catch (IOException e) { 213 if (e instanceof InterruptedIOException) { 214 Thread.currentThread().interrupt(); 215 } 216 // There is do need to invoke an error handler at this late 217 // stage. 218 LOGGER.error("Could not close " + qw, e); 219 } 220 } 221 } 222 223 /** 224 * Returns an OutputStreamWriter when passed an OutputStream. The 225 * encoding used will depend on the value of the 226 * <code>encoding</code> property. If the encoding value is 227 * specified incorrectly the writer will be opened using the default 228 * system encoding (an error message will be printed to the LOGGER. 229 * @param os The OutputStream. 230 * @return The OutputStreamWriter. 231 */ 232 protected OutputStreamWriter createWriter(OutputStream os) { 233 OutputStreamWriter retval = null; 234 235 String enc = getEncoding(); 236 if (enc != null) { 237 try { 238 retval = new OutputStreamWriter(os, enc); 239 } catch (IOException e) { 240 if (e instanceof InterruptedIOException) { 241 Thread.currentThread().interrupt(); 242 } 243 LOGGER.warn("Error initializing output writer."); 244 LOGGER.warn("Unsupported encoding?"); 245 } 246 } 247 if (retval == null) { 248 retval = new OutputStreamWriter(os); 249 } 250 return retval; 251 } 252 253 public String getEncoding() { 254 return encoding; 255 } 256 257 public void setEncoding(String value) { 258 encoding = value; 259 } 260 261 262 /** 263 * Set the {@link ErrorHandler} for this WriterAppender and also the 264 * underlying {@link QuietWriter} if any. 265 */ 266 public synchronized void setErrorHandler(ErrorHandler eh) { 267 if (eh == null) { 268 LOGGER.warn("You have tried to set a null error-handler."); 269 } else { 270 this.errorHandler = eh; 271 if (this.qw != null) { 272 this.qw.setErrorHandler(eh); 273 } 274 } 275 } 276 277 /** 278 * <p>Sets the Writer where the log output will go. The 279 * specified Writer must be opened by the user and be 280 * writable. 281 * 282 * <p>The <code>java.io.Writer</code> will be closed when the 283 * appender instance is closed. 284 * 285 * 286 * <p><b>WARNING:</b> Logging to an unopened Writer will fail. 287 * <p> 288 * 289 * @param writer An already opened Writer. 290 */ 291 public synchronized void setWriter(Writer writer) { 292 reset(); 293 this.qw = new QuietWriter(writer, errorHandler); 294 //this.tp = new TracerPrintWriter(qw); 295 writeHeader(); 296 } 297 298 299 /** 300 * Actual writing occurs here. 301 * 302 * <p>Most subclasses of <code>WriterAppender</code> will need to 303 * override this method. 304 * @param event The event to log. 305 * 306 * @since 0.9.0 307 */ 308 protected void subAppend(LoggingEvent event) { 309 this.qw.write(this.layout.format(event)); 310 311 if (layout.ignoresThrowable()) { 312 String[] s = event.getThrowableStrRep(); 313 if (s != null) { 314 int len = s.length; 315 for (int i = 0; i < len; i++) { 316 this.qw.write(s[i]); 317 this.qw.write(Layout.LINE_SEP); 318 } 319 } 320 } 321 322 if (shouldFlush(event)) { 323 this.qw.flush(); 324 } 325 } 326 327 328 /** 329 * The WriterAppender requires a layout. Hence, this method returns 330 * <code>true</code>. 331 */ 332 public boolean requiresLayout() { 333 return true; 334 } 335 336 /** 337 * Clear internal references to the writer and other variables. 338 * <p> 339 * Subclasses can override this method for an alternate closing 340 * behavior. 341 */ 342 protected void reset() { 343 closeWriter(); 344 this.qw = null; 345 //this.tp = null; 346 } 347 348 349 /** 350 * Write a footer as produced by the embedded layout's {@link 351 * Layout#getFooter} method. 352 */ 353 protected void writeFooter() { 354 if (layout != null) { 355 String f = layout.getFooter(); 356 if (f != null && this.qw != null) { 357 this.qw.write(f); 358 this.qw.flush(); 359 } 360 } 361 } 362 363 /** 364 * Write a header as produced by the embedded layout's {@link 365 * Layout#getHeader} method. 366 */ 367 protected void writeHeader() { 368 if (layout != null) { 369 String h = layout.getHeader(); 370 if (h != null && this.qw != null) { 371 this.qw.write(h); 372 } 373 } 374 } 375 376 /** 377 * Determines whether the writer should be flushed after 378 * this event is written. 379 * @param event The event to log. 380 * @return true if the writer should be flushed. 381 * 382 * @since 1.2.16 383 */ 384 protected boolean shouldFlush(final LoggingEvent event) { 385 return immediateFlush; 386 } 387 }