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