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 081 if (encoder == null) { 082 addWarn("Encoder has not been set. Cannot invoke its init method."); 083 errors++; 084 } 085 086 087 // only error free appenders should be activated 088 if (errors == 0) { 089 super.start(); 090 encoderInit(); 091 } 092 } 093 094 public void setLayout(Layout<E> layout) { 095 addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead."); 096 addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder."); 097 addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details"); 098 LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>(); 099 lwe.setLayout(layout); 100 lwe.setContext(context); 101 this.encoder = lwe; 102 } 103 104 @Override 105 protected void append(E eventObject) { 106 if (!isStarted()) { 107 return; 108 } 109 110 subAppend(eventObject); 111 } 112 113 /** 114 * Stop this appender instance. The underlying stream or writer is also closed. 115 * 116 * <p> 117 * Stopped appenders cannot be reused. 118 */ 119 public void stop() { 120 if(!isStarted()) 121 return; 122 123 streamWriteLock.lock(); 124 try { 125 closeOutputStream(); 126 super.stop(); 127 } finally { 128 streamWriteLock.unlock(); 129 } 130 } 131 132 /** 133 * Close the underlying {@link OutputStream}. 134 */ 135 protected void closeOutputStream() { 136 if (this.outputStream != null) { 137 try { 138 // before closing we have to output out layout's footer 139 encoderClose(); 140 this.outputStream.close(); 141 this.outputStream = null; 142 } catch (IOException e) { 143 addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e)); 144 } 145 } 146 } 147 148 void encoderClose() { 149 if (encoder != null && this.outputStream != null) { 150 try { 151 byte[] footer = encoder.footerBytes(); 152 writeBytes(footer); 153 } catch (IOException ioe) { 154 this.started = false; 155 addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe)); 156 } 157 } 158 } 159 160 /** 161 * <p> 162 * Sets the @link OutputStream} where the log output will go. The specified 163 * <code>OutputStream</code> must be opened by the user and be writable. The 164 * <code>OutputStream</code> will be closed when the appender instance is 165 * closed. 166 * 167 * @param outputStream An already opened OutputStream. 168 */ 169 public void setOutputStream(OutputStream outputStream) { 170 streamWriteLock.lock(); 171 try { 172 // close any previously opened output stream 173 closeOutputStream(); 174 this.outputStream = outputStream; 175 176 } finally { 177 streamWriteLock.unlock(); 178 } 179 } 180 181 void encoderInit() { 182 if (encoder != null && this.outputStream != null) { 183 try { 184 byte[] header = encoder.headerBytes(); 185 writeBytes(header); 186 } catch (IOException ioe) { 187 this.started = false; 188 addStatus( 189 new ErrorStatus("Failed to initialize encoder for appender named [" + name + "].", this, ioe)); 190 } 191 } 192 } 193 194 protected void writeOut(E event) throws IOException { 195 byte[] byteArray = this.encoder.encode(event); 196 writeBytes(byteArray); 197 } 198 199 private void writeBytes(byte[] byteArray) throws IOException { 200 if (byteArray == null || byteArray.length == 0) 201 return; 202 203 streamWriteLock.lock(); 204 205 try { 206 if(isStarted()) { 207 writeByteArrayToOutputStreamWithPossibleFlush(byteArray); 208 } 209 } finally { 210 streamWriteLock.unlock(); 211 } 212 } 213 214 /** 215 * A simple method to write to an outputStream and flush the stream if immediateFlush is set to true. 216 * 217 * @since 1.3.9/1.4.9 218 */ 219 protected final void writeByteArrayToOutputStreamWithPossibleFlush(byte[] byteArray) throws IOException { 220 this.outputStream.write(byteArray); 221 if (immediateFlush) { 222 this.outputStream.flush(); 223 } 224 } 225 226 /** 227 * Actual writing occurs here. 228 * <p> 229 * Most subclasses of <code>WriterAppender</code> will need to override this 230 * method. 231 * 232 * @since 0.9.0 233 */ 234 protected void subAppend(E event) { 235 if (!isStarted()) { 236 return; 237 } 238 try { 239 // this step avoids LBCLASSIC-139 240 if (event instanceof DeferredProcessingAware) { 241 ((DeferredProcessingAware) event).prepareForDeferredProcessing(); 242 } 243 writeOut(event); 244 245 } catch (IOException ioe) { 246 // as soon as an exception occurs, move to non-started state 247 // and add a single ErrorStatus to the SM. 248 this.started = false; 249 addStatus(new ErrorStatus("IO failure in appender", this, ioe)); 250 } 251 } 252 253 public Encoder<E> getEncoder() { 254 return encoder; 255 } 256 257 public void setEncoder(Encoder<E> encoder) { 258 this.encoder = encoder; 259 } 260 261 public boolean isImmediateFlush() { 262 return immediateFlush; 263 } 264 265 public void setImmediateFlush(boolean immediateFlush) { 266 this.immediateFlush = immediateFlush; 267 } 268 269}