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