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>Stopped appenders cannot be reused.</p> 124 */ 125 public void stop() { 126 if(!isStarted()) 127 return; 128 129 streamWriteLock.lock(); 130 try { 131 closeOutputStream(); 132 super.stop(); 133 } finally { 134 streamWriteLock.unlock(); 135 } 136 } 137 138 /** 139 * Close the underlying {@link OutputStream}. 140 */ 141 protected void closeOutputStream() { 142 if (this.outputStream != null) { 143 try { 144 // before closing we have to output out encooder's footer 145 encoderClose(); 146 this.outputStream.close(); 147 this.outputStream = null; 148 } catch (IOException e) { 149 addStatus(new ErrorStatus("Could not close output stream for OutputStreamAppender.", this, e)); 150 } 151 } 152 } 153 154 /** 155 * Write the encoder's footer to the underlying {@link OutputStream}. 156 * 157 * <p>It is assumed that the caller has acquired the streamWriteLock.</p> 158 */ 159 void encoderClose() { 160 if (encoder != null && this.outputStream != null) { 161 try { 162 byte[] footer = encoder.footerBytes(); 163 writeBytes(footer); 164 } catch (IOException ioe) { 165 this.started = false; 166 addStatus(new ErrorStatus("Failed to write footer for appender named [" + name + "].", this, ioe)); 167 } 168 } 169 } 170 171 /** 172 * <p> 173 * Sets the {@link OutputStream} where the log output will go. The specified 174 * <code>OutputStream</code> must be opened by the user and be writable. The 175 * <code>OutputStream</code> will be closed when the appender instance is 176 * closed. 177 * </p> 178 * 179 * @param outputStream An already opened OutputStream. 180 */ 181 public void setOutputStream(OutputStream outputStream) { 182 streamWriteLock.lock(); 183 try { 184 // close any previously opened output stream 185 closeOutputStream(); 186 this.outputStream = outputStream; 187 188 // the first call to setOutputStream() is made on a non started appender 189 // However, in subsequent calls to setOutputStream() the appender will be in 190 // started state. In subsequent calls, in particular when opening a file after rollover, 191 // we have to output the header hence the call to encoderInit(). 192 if(isStarted()) { 193 encoderInit(); 194 } 195 } finally { 196 streamWriteLock.unlock(); 197 } 198 } 199 200 /** 201 * <p>Write the encoder's header to the underlying {@link OutputStream}.</p> 202 * 203 * <p>It is assumed that the caller has acquired the streamWriteLock.</p> 204 */ 205 void encoderInit() { 206 if (encoder != null && this.outputStream != null) { 207 try { 208 byte[] header = encoder.headerBytes(); 209 writeBytes(header); 210 } catch (IOException ioe) { 211 this.started = false; 212 addError("Failed to initialize encoder for appender named [" + name + "].", ioe); 213 } 214 } 215 } 216 217 protected void writeOut(E event) throws IOException { 218 if (statefulEncoder) { 219 writeOutStateful(event); 220 return; 221 } 222 writeBytes(this.encoder.encode(event)); 223 } 224 225 private void writeOutStateful(E event) throws IOException { 226 streamWriteLock.lock(); 227 try { 228 // Recheck under the lock: stop() may have written the footer since 229 // subAppend() observed isStarted(). 230 if (!isStarted()) { 231 return; 232 } 233 writeBytes(this.encoder.encode(event)); 234 } finally { 235 streamWriteLock.unlock(); 236 } 237 } 238 239 private void writeBytes(byte[] byteArray) throws IOException { 240 if (byteArray == null || byteArray.length == 0) 241 return; 242 243 streamWriteLock.lock(); 244 try { 245 // guard against appender with stop() invoked in parallel 246 if(isStarted()) { 247 writeByteArrayToOutputStreamWithPossibleFlush(byteArray); 248 updateByteCount(byteArray); 249 } 250 } finally { 251 streamWriteLock.unlock(); 252 } 253 } 254 255 protected void updateByteCount(byte[] byteArray) { 256 } 257 258 /** 259 * A simple method to write to an outputStream and flush the stream if immediateFlush is set to true. 260 * 261 * @since 1.3.9/1.4.9 262 */ 263 protected final void writeByteArrayToOutputStreamWithPossibleFlush(byte[] byteArray) throws IOException { 264 this.outputStream.write(byteArray); 265 if (immediateFlush) { 266 this.outputStream.flush(); 267 } 268 } 269 270 /** 271 * Actual writing occurs here. 272 * <p> 273 * Most subclasses of <code>WriterAppender</code> will need to override this 274 * method. 275 * 276 * @since 0.9.0 277 */ 278 protected void subAppend(E event) { 279 if (!isStarted()) { 280 return; 281 } 282 try { 283 // this step avoids LBCLASSIC-139 284 if (event instanceof DeferredProcessingAware) { 285 ((DeferredProcessingAware) event).prepareForDeferredProcessing(); 286 } 287 writeOut(event); 288 289 } catch (IOException ioe) { 290 // as soon as an exception occurs, move to non-started state 291 // and add a single ErrorStatus to the SM. 292 this.started = false; 293 addStatus(new ErrorStatus("IO failure in appender", this, ioe)); 294 } 295 } 296 297 public Encoder<E> getEncoder() { 298 return encoder; 299 } 300 301 public void setEncoder(Encoder<E> encoder) { 302 this.encoder = encoder; 303 } 304 305 public boolean isImmediateFlush() { 306 return immediateFlush; 307 } 308 309 public void setImmediateFlush(boolean immediateFlush) { 310 this.immediateFlush = immediateFlush; 311 } 312 313}