001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.appender; 018 019import java.io.IOException; 020import java.io.OutputStream; 021import java.nio.ByteBuffer; 022import java.util.Objects; 023 024import org.apache.logging.log4j.core.Layout; 025import org.apache.logging.log4j.core.layout.ByteBufferDestination; 026import org.apache.logging.log4j.core.util.Constants; 027 028/** 029 * Manages an OutputStream so that it can be shared by multiple Appenders and will 030 * allow appenders to reconfigure without requiring a new stream. 031 */ 032public class OutputStreamManager extends AbstractManager implements ByteBufferDestination { 033 protected final Layout<?> layout; 034 protected ByteBuffer byteBuffer; 035 private volatile OutputStream os; 036 private boolean skipFooter = false; 037 038 protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout, 039 final boolean writeHeader) { 040 this(os, streamName, layout, writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE])); 041 } 042 043 /** 044 * 045 * @param os 046 * @param streamName 047 * @param layout 048 * @param writeHeader 049 * @param byteBuffer 050 * @since 2.6 051 */ 052 protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout, 053 final boolean writeHeader, final ByteBuffer byteBuffer) { 054 super(streamName); 055 this.os = os; 056 this.layout = layout; 057 if (writeHeader && layout != null) { 058 final byte[] header = layout.getHeader(); 059 if (header != null) { 060 try { 061 this.os.write(header, 0, header.length); 062 } catch (final IOException e) { 063 logError("unable to write header", e); 064 } 065 } 066 } 067 this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer"); 068 } 069 070 /** 071 * Creates a Manager. 072 * 073 * @param name The name of the stream to manage. 074 * @param data The data to pass to the Manager. 075 * @param factory The factory to use to create the Manager. 076 * @param <T> The type of the OutputStreamManager. 077 * @return An OutputStreamManager. 078 */ 079 public static <T> OutputStreamManager getManager(final String name, final T data, 080 final ManagerFactory<? extends OutputStreamManager, T> factory) { 081 return AbstractManager.getManager(name, factory, data); 082 } 083 084 /** 085 * Indicate whether the footer should be skipped or not. 086 * @param skipFooter true if the footer should be skipped. 087 */ 088 public void skipFooter(boolean skipFooter) { 089 this.skipFooter = skipFooter; 090 } 091 092 /** 093 * Default hook to write footer during close. 094 */ 095 @Override 096 public void releaseSub() { 097 writeFooter(); 098 close(); 099 } 100 101 /** 102 * Writes the footer. 103 */ 104 protected void writeFooter() { 105 if (layout == null || skipFooter) { 106 return; 107 } 108 final byte[] footer = layout.getFooter(); 109 if (footer != null) { 110 write(footer); 111 } 112 } 113 114 /** 115 * Returns the status of the stream. 116 * @return true if the stream is open, false if it is not. 117 */ 118 public boolean isOpen() { 119 return getCount() > 0; 120 } 121 122 protected OutputStream getOutputStream() { 123 return os; 124 } 125 126 protected void setOutputStream(final OutputStream os) { 127 final byte[] header = layout.getHeader(); 128 if (header != null) { 129 try { 130 os.write(header, 0, header.length); 131 this.os = os; // only update field if os.write() succeeded 132 } catch (final IOException ioe) { 133 logError("unable to write header", ioe); 134 } 135 } else { 136 this.os = os; 137 } 138 } 139 140 /** 141 * Some output streams synchronize writes while others do not. 142 * @param bytes The serialized Log event. 143 * @throws AppenderLoggingException if an error occurs. 144 */ 145 protected void write(final byte[] bytes) { 146 write(bytes, 0, bytes.length, false); 147 } 148 149 /** 150 * Some output streams synchronize writes while others do not. 151 * @param bytes The serialized Log event. 152 * @param immediateFlush If true, flushes after writing. 153 * @throws AppenderLoggingException if an error occurs. 154 */ 155 protected void write(final byte[] bytes, final boolean immediateFlush) { 156 write(bytes, 0, bytes.length, immediateFlush); 157 } 158 159 /** 160 * Some output streams synchronize writes while others do not. Synchronizing here insures that 161 * log events won't be intertwined. 162 * @param bytes The serialized Log event. 163 * @param offset The offset into the byte array. 164 * @param length The number of bytes to write. 165 * @throws AppenderLoggingException if an error occurs. 166 */ 167 protected void write(final byte[] bytes, final int offset, final int length) { 168 write(bytes, offset, length, false); 169 } 170 171 /** 172 * Some output streams synchronize writes while others do not. Synchronizing here insures that 173 * log events won't be intertwined. 174 * @param bytes The serialized Log event. 175 * @param offset The offset into the byte array. 176 * @param length The number of bytes to write. 177 * @param immediateFlush flushes immediately after writing. 178 * @throws AppenderLoggingException if an error occurs. 179 */ 180 protected synchronized void write(final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { 181 if (immediateFlush && byteBuffer.position() == 0) { 182 writeToDestination(bytes, offset, length); 183 flushDestination(); 184 return; 185 } 186 if (length >= byteBuffer.capacity()) { 187 // if request length exceeds buffer capacity, flush the buffer and write the data directly 188 flush(); 189 writeToDestination(bytes, offset, length); 190 } else { 191 if (length > byteBuffer.remaining()) { 192 flush(); 193 } 194 byteBuffer.put(bytes, offset, length); 195 } 196 if (immediateFlush) { 197 flush(); 198 } 199 } 200 201 /** 202 * Writes the specified section of the specified byte array to the stream. 203 * 204 * @param bytes the array containing data 205 * @param offset from where to write 206 * @param length how many bytes to write 207 * @since 2.6 208 */ 209 protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { 210 try { 211 os.write(bytes, offset, length); 212 } catch (final IOException ex) { 213 final String msg = "Error writing to stream " + getName(); 214 throw new AppenderLoggingException(msg, ex); 215 } 216 } 217 218 /** 219 * Calls {@code flush()} on the underlying output stream. 220 * @since 2.6 221 */ 222 protected synchronized void flushDestination() { 223 try { 224 os.flush(); 225 } catch (final IOException ex) { 226 final String msg = "Error flushing stream " + getName(); 227 throw new AppenderLoggingException(msg, ex); 228 } 229 } 230 231 /** 232 * Drains the ByteBufferDestination's buffer into the destination. By default this calls 233 * {@link OutputStreamManager#write(byte[], int, int, boolean)} with the buffer contents. 234 * The underlying stream is not {@linkplain OutputStream#flush() flushed}. 235 * 236 * @see #flushDestination() 237 * @since 2.6 238 */ 239 protected synchronized void flushBuffer(final ByteBuffer buf) { 240 buf.flip(); 241 if (buf.limit() > 0) { 242 writeToDestination(buf.array(), 0, buf.limit()); 243 } 244 buf.clear(); 245 } 246 247 /** 248 * Flushes any buffers. 249 */ 250 public synchronized void flush() { 251 flushBuffer(byteBuffer); 252 flushDestination(); 253 } 254 255 protected synchronized void close() { 256 flush(); 257 final OutputStream stream = os; // access volatile field only once per method 258 if (stream == System.out || stream == System.err) { 259 return; 260 } 261 try { 262 stream.close(); 263 } catch (final IOException ex) { 264 logError("unable to close stream", ex); 265 } 266 } 267 268 /** 269 * Returns this {@code ByteBufferDestination}'s buffer. 270 * @return the buffer 271 * @since 2.6 272 */ 273 @Override 274 public ByteBuffer getByteBuffer() { 275 return byteBuffer; 276 } 277 278 /** 279 * Drains the ByteBufferDestination's buffer into the destination. By default this calls 280 * {@link #flushBuffer(ByteBuffer)} with the specified buffer. Subclasses may override. 281 * <p> 282 * Do not call this method lightly! For some subclasses this is a very expensive operation. For example, 283 * {@link MemoryMappedFileManager} will assume this method was called because the end of the mapped region 284 * was reached during a text encoding operation and will {@linkplain MemoryMappedFileManager#remap() remap} its 285 * buffer. 286 * </p><p> 287 * To just flush the buffered contents to the underlying stream, call 288 * {@link #flushBuffer(ByteBuffer)} directly instead. 289 * </p> 290 * 291 * @param buf the buffer whose contents to write the the destination 292 * @return the specified buffer 293 * @since 2.6 294 */ 295 @Override 296 public ByteBuffer drain(final ByteBuffer buf) { 297 flushBuffer(buf); 298 return buf; 299 } 300}