001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, 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 v2.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 boolean statefulEncoder = false; 058 059 /** 060 * The underlying output stream used by this appender. 061 * 062 * @return 063 */ 064 public OutputStream getOutputStream() { 065 return outputStream; 066 } 067 068 /** 069 * Checks that requires parameters are set and if everything is in order, 070 * activates this appender. 071 */ 072 public void start() { 073 int errors = 0; 074 if (this.encoder == null) { 075 addError("No encoder set for the appender named \"" + name + "\"."); 076 addWarn("Encoder has not been set. Cannot invoke its init method."); 077 errors++; 078 } 079 080 if (this.outputStream == null) { 081 addError("No output stream set for the appender named \"" + name + "\"."); 082 errors++; 083 } 084 085 086 // only error free appenders should be activated 087 if (errors == 0) { 088 this.statefulEncoder = encoder.isStateful(); 089 // Hold the lock across started=true and headerBytes so a concurrent 090 // append cannot encode before the header is written. 091 streamWriteLock.lock(); 092 try { 093 super.start(); 094 encoderInit(); 095 } finally { 096 streamWriteLock.unlock(); 097 } 098 } 099 } 100 101 public void setLayout(Layout<E> layout) { 102 addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead."); 103 addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder."); 104 addWarn("See also " + CODES_URL + "#layoutInsteadOfEncoder for details"); 105 LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>(); 106 lwe.setLayout(layout); 107 lwe.setContext(context); 108 this.encoder = lwe; 109 } 110 111 @Override 112 protected void append(E eventObject) { 113 if (!isStarted()) { 114 return; 115 } 116 117 subAppend(eventObject); 118 } 119 120 /** 121 * Stop this appender instance. The underlying stream or writer is also closed. 122 * 123 * <p>The encoder is stopped as well, after the footer has been written and the stream closed, so that any 124 * resources it holds (for instance per-thread buffers) are released. Otherwise, each context reset or 125 * reconfiguration would leave the previous encoder and its resources behind.</p> 126 * 127 * <p>Stopped appenders cannot be reused.</p> 128 */ 129 public void stop() { 130 if(!isStarted()) 131 return; 132 133 streamWriteLock.lock(); 134 try { 135 closeOutputStream(); 136 super.stop(); 137 stopEncoder(); 138 } finally { 139 streamWriteLock.unlock(); 140 } 141 } 142 143 /** 144 * Stop the encoder, if it is started. 145 * 146 * <p>It is assumed that the caller has acquired the streamWriteLock.</p> 147 */ 148 private void stopEncoder() { 149 if (encoder != null && encoder.isStarted()) { 150 encoder.stop(); 151 } 152 } 153 154 /** 155 * Close the underlying {@link OutputStream}. 156 */ 157 protected void closeOutputStream() { 158 if (this.outputStream != null) { 159 try { 160 // before closing we have to output out encooder's footer 161 encoderClose(); 162 this.outputStream.close(); 163 this.outputStream = null; 164 } catch (IOException e) { 165 addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e)); 166 } 167 } 168 } 169 170 /** 171 * Write the encoder's footer to the underlying {@link OutputStream}. 172 * 173 * <p>It is assumed that the caller has acquired the streamWriteLock.</p> 174 */ 175 void encoderClose() { 176 if (encoder != null && this.outputStream != null) { 177 try { 178 byte[] footer = encoder.footerBytes(); 179 writeBytes(footer); 180 } catch (IOException ioe) { 181 this.started = false; 182 addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe)); 183 } 184 } 185 } 186 187 /** 188 * <p> 189 * Sets the {@link OutputStream} where the log output will go. The specified 190 * <code>OutputStream</code> must be opened by the user and be writable. The 191 * <code>OutputStream</code> will be closed when the appender instance is 192 * closed. 193 * </p> 194 * 195 * @param outputStream An already opened OutputStream. 196 */ 197 public void setOutputStream(OutputStream outputStream) { 198 streamWriteLock.lock(); 199 try { 200 // close any previously opened output stream 201 closeOutputStream(); 202 this.outputStream = outputStream; 203 204 // the first call to setOutputStream() is made on a non started appender 205 // However, in subsequent calls to setOutputStream() the appender will be in 206 // started state. In subsequent calls, in particular when opening a file after rollover, 207 // we have to output the header hence the call to encoderInit(). 208 if(isStarted()) { 209 encoderInit(); 210 } 211 } finally { 212 streamWriteLock.unlock(); 213 } 214 } 215 216 /** 217 * <p>Write the encoder's header to the underlying {@link OutputStream}.</p> 218 * 219 * <p>It is assumed that the caller has acquired the streamWriteLock.</p> 220 */ 221 void encoderInit() { 222 if (encoder != null && this.outputStream != null) { 223 try { 224 byte[] header = encoder.headerBytes(); 225 writeBytes(header); 226 } catch (IOException ioe) { 227 this.started = false; 228 addError("Failed to initialize encoder for appender named [" + name + "].", ioe); 229 } 230 } 231 } 232 233 protected void writeOut(E event) throws IOException { 234 if (statefulEncoder) { 235 writeOutStateful(event); 236 return; 237 } 238 writeBytes(this.encoder.encode(event)); 239 } 240 241 private void writeOutStateful(E event) throws IOException { 242 streamWriteLock.lock(); 243 try { 244 // Recheck under the lock: stop() may have written the footer since 245 // subAppend() observed isStarted(). 246 if (!isStarted()) { 247 return; 248 } 249 writeBytes(this.encoder.encode(event)); 250 } finally { 251 streamWriteLock.unlock(); 252 } 253 } 254 255 private void writeBytes(byte[] byteArray) throws IOException { 256 if (byteArray == null || byteArray.length == 0) 257 return; 258 259 streamWriteLock.lock(); 260 try { 261 // guard against appender with stop() invoked in parallel 262 if(isStarted()) { 263 writeByteArrayToOutputStreamWithPossibleFlush(byteArray); 264 updateByteCount(byteArray); 265 } 266 } finally { 267 streamWriteLock.unlock(); 268 } 269 } 270 271 protected void updateByteCount(byte[] byteArray) { 272 } 273 274 /** 275 * A simple method to write to an outputStream and flush the stream if immediateFlush is set to true. 276 * 277 * @since 1.3.9/1.4.9 278 */ 279 protected final void writeByteArrayToOutputStreamWithPossibleFlush(byte[] byteArray) throws IOException { 280 this.outputStream.write(byteArray); 281 if (immediateFlush) { 282 this.outputStream.flush(); 283 } 284 } 285 286 /** 287 * Actual writing occurs here. 288 * <p> 289 * Most subclasses of <code>WriterAppender</code> will need to override this 290 * method. 291 * 292 * @since 0.9.0 293 */ 294 protected void subAppend(E event) { 295 if (!isStarted()) { 296 return; 297 } 298 try { 299 // this step avoids LBCLASSIC-139 300 if (event instanceof DeferredProcessingAware) { 301 ((DeferredProcessingAware) event).prepareForDeferredProcessing(); 302 } 303 writeOut(event); 304 305 } catch (IOException ioe) { 306 // as soon as an exception occurs, move to non-started state 307 // and add a single ErrorStatus to the SM. 308 this.started = false; 309 addStatus(new ErrorStatus("IO failure in appender", this, ioe)); 310 } 311 } 312 313 public Encoder<E> getEncoder() { 314 return encoder; 315 } 316 317 public void setEncoder(Encoder<E> encoder) { 318 this.encoder = encoder; 319 } 320 321 public boolean isImmediateFlush() { 322 return immediateFlush; 323 } 324 325 public void setImmediateFlush(boolean immediateFlush) { 326 this.immediateFlush = immediateFlush; 327 } 328 329}