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 layout'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 } finally { 178 streamWriteLock.unlock(); 179 } 180 } 181 182 void encoderInit() { 183 if (encoder != null && this.outputStream != null) { 184 try { 185 byte[] header = encoder.headerBytes(); 186 writeBytes(header); 187 } catch (IOException ioe) { 188 this.started = false; 189 addStatus( 190 new ErrorStatus("Failed to initialize encoder for appender named [" + name + "].", this, ioe)); 191 } 192 } 193 } 194 195 protected void writeOut(E event) throws IOException { 196 byte[] byteArray = this.encoder.encode(event); 197 writeBytes(byteArray); 198 } 199 200 private void writeBytes(byte[] byteArray) throws IOException { 201 if (byteArray == null || byteArray.length == 0) 202 return; 203 204 streamWriteLock.lock(); 205 206 try { 207 if(isStarted()) { 208 writeByteArrayToOutputStreamWithPossibleFlush(byteArray); 209 updateByteCount(byteArray); 210 } 211 } finally { 212 streamWriteLock.unlock(); 213 } 214 } 215 216 protected void updateByteCount(byte[] byteArray) { 217 } 218 219 /** 220 * A simple method to write to an outputStream and flush the stream if immediateFlush is set to true. 221 * 222 * @since 1.3.9/1.4.9 223 */ 224 protected final void writeByteArrayToOutputStreamWithPossibleFlush(byte[] byteArray) throws IOException { 225 this.outputStream.write(byteArray); 226 if (immediateFlush) { 227 this.outputStream.flush(); 228 } 229 } 230 231 /** 232 * Actual writing occurs here. 233 * <p> 234 * Most subclasses of <code>WriterAppender</code> will need to override this 235 * method. 236 * 237 * @since 0.9.0 238 */ 239 protected void subAppend(E event) { 240 if (!isStarted()) { 241 return; 242 } 243 try { 244 // this step avoids LBCLASSIC-139 245 if (event instanceof DeferredProcessingAware) { 246 ((DeferredProcessingAware) event).prepareForDeferredProcessing(); 247 } 248 writeOut(event); 249 250 } catch (IOException ioe) { 251 // as soon as an exception occurs, move to non-started state 252 // and add a single ErrorStatus to the SM. 253 this.started = false; 254 addStatus(new ErrorStatus("IO failure in appender", this, ioe)); 255 } 256 } 257 258 public Encoder<E> getEncoder() { 259 return encoder; 260 } 261 262 public void setEncoder(Encoder<E> encoder) { 263 this.encoder = encoder; 264 } 265 266 public boolean isImmediateFlush() { 267 return immediateFlush; 268 } 269 270 public void setImmediateFlush(boolean immediateFlush) { 271 this.immediateFlush = immediateFlush; 272 } 273 274}