View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.appender;
18  
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.nio.ByteBuffer;
22  import java.util.Objects;
23  
24  import org.apache.logging.log4j.core.Layout;
25  import org.apache.logging.log4j.core.layout.ByteBufferDestination;
26  import org.apache.logging.log4j.core.util.Constants;
27  
28  /**
29   * Manages an OutputStream so that it can be shared by multiple Appenders and will
30   * allow appenders to reconfigure without requiring a new stream.
31   */
32  public class OutputStreamManager extends AbstractManager implements ByteBufferDestination {
33      protected final Layout<?> layout;
34      protected ByteBuffer byteBuffer;
35      private volatile OutputStream os;
36      private boolean skipFooter;
37  
38      protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout,
39              final boolean writeHeader) {
40          this(os, streamName, layout, writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE]));
41      }
42  
43      /**
44       *
45       * @param os
46       * @param streamName
47       * @param layout
48       * @param writeHeader
49       * @param byteBuffer
50       * @since 2.6
51       */
52      protected OutputStreamManager(final OutputStream os, final String streamName, final Layout<?> layout,
53              final boolean writeHeader, final ByteBuffer byteBuffer) {
54          super(streamName);
55          this.os = os;
56          this.layout = layout;
57          if (writeHeader && layout != null) {
58              final byte[] header = layout.getHeader();
59              if (header != null) {
60                  try {
61                      this.os.write(header, 0, header.length);
62                  } catch (final IOException e) {
63                      logError("Unable to write header", e);
64                  }
65              }
66          }
67          this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer");
68      }
69  
70      /**
71       * Creates a Manager.
72       *
73       * @param name The name of the stream to manage.
74       * @param data The data to pass to the Manager.
75       * @param factory The factory to use to create the Manager.
76       * @param <T> The type of the OutputStreamManager.
77       * @return An OutputStreamManager.
78       */
79      public static <T> OutputStreamManager getManager(final String name, final T data,
80                                                   final ManagerFactory<? extends OutputStreamManager, T> factory) {
81          return AbstractManager.getManager(name, factory, data);
82      }
83  
84      /**
85       * Indicate whether the footer should be skipped or not.
86       * @param skipFooter true if the footer should be skipped.
87       */
88      public void skipFooter(final boolean skipFooter) {
89          this.skipFooter = skipFooter;
90      }
91  
92      /**
93       * Default hook to write footer during close.
94       */
95      @Override
96      public void releaseSub() {
97          writeFooter();
98          close();
99      }
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 }