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