001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core; 015 016import static ch.qos.logback.core.CoreConstants.CODES_URL; 017 018import java.io.IOException; 019import java.io.OutputStream; 020import java.util.concurrent.locks.ReentrantLock; 021 022import ch.qos.logback.core.encoder.Encoder; 023import ch.qos.logback.core.encoder.LayoutWrappingEncoder; 024import ch.qos.logback.core.rolling.LengthCounter; 025import ch.qos.logback.core.spi.DeferredProcessingAware; 026import ch.qos.logback.core.status.ErrorStatus; 027 028/** 029 * OutputStreamAppender appends events to a {@link OutputStream}. This class 030 * provides basic services that other appenders build upon. 031 * 032 * For more information about this appender, please refer to the online manual 033 * at http://logback.qos.ch/manual/appenders.html#OutputStreamAppender 034 * 035 * @author Ceki Gülcü 036 */ 037public class OutputStreamAppender<E> extends UnsynchronizedAppenderBase<E> { 038 039 /** 040 * It is the encoder which is ultimately responsible for writing the event to an 041 * {@link OutputStream}. 042 */ 043 protected Encoder<E> encoder; 044 045 /** 046 * All synchronization in this class is done via the lock object. 047 */ 048 protected final ReentrantLock streamWriteLock = new ReentrantLock(false); 049 050 /** 051 * This is the {@link OutputStream outputStream} where output will be written. 052 */ 053 private OutputStream outputStream; 054 055 boolean immediateFlush = true; 056 057 /** 058 * The underlying output stream used by this appender. 059 * 060 * @return 061 */ 062 public OutputStream getOutputStream() { 063 return outputStream; 064 } 065 066 /** 067 * Checks that requires parameters are set and if everything is in order, 068 * activates this appender. 069 */ 070 public void start() { 071 int errors = 0; 072 if (this.encoder == null) { 073 addStatus(new ErrorStatus("No encoder set for the appender named \"" + name + "\".", this)); 074 errors++; 075 } 076 077 if (this.outputStream == null) { 078 addStatus(new ErrorStatus("No output stream set for the appender named \"" + name + "\".", this)); 079 errors++; 080 } 081 082 if (encoder == null) { 083 addWarn("Encoder has not been set. Cannot invoke its init method."); 084 errors++; 085 } 086 087 088 // only error free appenders should be activated 089 if (errors == 0) { 090 super.start(); 091 encoderInit(); 092 } 093 } 094 095 public void setLayout(Layout<E> layout) { 096 addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead."); 097 addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder."); 098 addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details"); 099 LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>(); 100 lwe.setLayout(layout); 101 lwe.setContext(context); 102 this.encoder = lwe; 103 } 104 105 @Override 106 protected void append(E eventObject) { 107 if (!isStarted()) { 108 return; 109 } 110 111 subAppend(eventObject); 112 } 113 114 /** 115 * Stop this appender instance. The underlying stream or writer is also closed. 116 * 117 * <p> 118 * Stopped appenders cannot be reused. 119 */ 120 public void stop() { 121 if(!isStarted()) 122 return; 123 124 streamWriteLock.lock(); 125 try { 126 closeOutputStream(); 127 super.stop(); 128 } finally { 129 streamWriteLock.unlock(); 130 } 131 } 132 133 /** 134 * Close the underlying {@link OutputStream}. 135 */ 136 protected void closeOutputStream() { 137 if (this.outputStream != null) { 138 try { 139 // before closing we have to output out encooder's footer 140 encoderClose(); 141 this.outputStream.close(); 142 this.outputStream = null; 143 } catch (IOException e) { 144 addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e)); 145 } 146 } 147 } 148 149 void encoderClose() { 150 if (encoder != null && this.outputStream != null) { 151 try { 152 byte[] footer = encoder.footerBytes(); 153 writeBytes(footer); 154 } catch (IOException ioe) { 155 this.started = false; 156 addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe)); 157 } 158 } 159 } 160 161 /** 162 * <p> 163 * Sets the @link OutputStream} where the log output will go. The specified 164 * <code>OutputStream</code> must be opened by the user and be writable. The 165 * <code>OutputStream</code> will be closed when the appender instance is 166 * closed. 167 * 168 * @param outputStream An already opened OutputStream. 169 */ 170 public void setOutputStream(OutputStream outputStream) { 171 streamWriteLock.lock(); 172 try { 173 // close any previously opened output stream 174 closeOutputStream(); 175 this.outputStream = outputStream; 176 177 // the first call to setOutputStream() is made on a non started appender 178 // However, in subsequent calls to setOutputStream() the appender will be in 179 // started state. In subsequent calls, in particular when opening a file after rollover, 180 // we have to output the header hence the call to encoderInit(). 181 if(isStarted()) { 182 encoderInit(); 183 } 184 } finally { 185 streamWriteLock.unlock(); 186 } 187 } 188 189 void encoderInit() { 190 if (encoder != null && this.outputStream != null) { 191 try { 192 byte[] header = encoder.headerBytes(); 193 writeBytes(header); 194 } catch (IOException ioe) { 195 this.started = false; 196 addStatus( 197 new ErrorStatus("Failed to initialize encoder for appender named [" + name + "].", this, ioe)); 198 } 199 } 200 } 201 202 protected void writeOut(E event) throws IOException { 203 byte[] byteArray = this.encoder.encode(event); 204 writeBytes(byteArray); 205 } 206 207 private void writeBytes(byte[] byteArray) throws IOException { 208 if (byteArray == null || byteArray.length == 0) 209 return; 210 211 streamWriteLock.lock(); 212 213 try { 214 // guard against appender having been stop() in parallel 215 // note that the encoding step is performed outside the protection of the streamWriteLock 216 if(isStarted()) { 217 writeByteArrayToOutputStreamWithPossibleFlush(byteArray); 218 updateByteCount(byteArray); 219 } 220 } finally { 221 streamWriteLock.unlock(); 222 } 223 } 224 225 protected void updateByteCount(byte[] byteArray) { 226 } 227 228 /** 229 * A simple method to write to an outputStream and flush the stream if immediateFlush is set to true. 230 * 231 * @since 1.3.9/1.4.9 232 */ 233 protected final void writeByteArrayToOutputStreamWithPossibleFlush(byte[] byteArray) throws IOException { 234 this.outputStream.write(byteArray); 235 if (immediateFlush) { 236 this.outputStream.flush(); 237 } 238 } 239 240 /** 241 * Actual writing occurs here. 242 * <p> 243 * Most subclasses of <code>WriterAppender</code> will need to override this 244 * method. 245 * 246 * @since 0.9.0 247 */ 248 protected void subAppend(E event) { 249 if (!isStarted()) { 250 return; 251 } 252 try { 253 // this step avoids LBCLASSIC-139 254 if (event instanceof DeferredProcessingAware) { 255 ((DeferredProcessingAware) event).prepareForDeferredProcessing(); 256 } 257 writeOut(event); 258 259 } catch (IOException ioe) { 260 // as soon as an exception occurs, move to non-started state 261 // and add a single ErrorStatus to the SM. 262 this.started = false; 263 addStatus(new ErrorStatus("IO failure in appender", this, ioe)); 264 } 265 } 266 267 public Encoder<E> getEncoder() { 268 return encoder; 269 } 270 271 public void setEncoder(Encoder<E> encoder) { 272 this.encoder = encoder; 273 } 274 275 public boolean isImmediateFlush() { 276 return immediateFlush; 277 } 278 279 public void setImmediateFlush(boolean immediateFlush) { 280 this.immediateFlush = immediateFlush; 281 } 282 283}