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.File;
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.io.RandomAccessFile;
23  import java.io.Serializable;
24  import java.nio.ByteBuffer;
25  import java.util.HashMap;
26  import java.util.Map;
27  
28  import org.apache.logging.log4j.core.Layout;
29  import org.apache.logging.log4j.core.LoggerContext;
30  import org.apache.logging.log4j.core.config.Configuration;
31  import org.apache.logging.log4j.core.util.NullOutputStream;
32  
33  /**
34   * Extends OutputStreamManager but instead of using a buffered output stream,
35   * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
36   * I/O.
37   */
38  public class RandomAccessFileManager extends OutputStreamManager {
39      static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
40  
41      private static final RandomAccessFileManagerFactory FACTORY = new RandomAccessFileManagerFactory();
42  
43      private final String advertiseURI;
44      private final RandomAccessFile randomAccessFile;
45      private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>();
46  
47      protected RandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile file, final String fileName,
48              final OutputStream os, final int bufferSize, final String advertiseURI,
49              final Layout<? extends Serializable> layout, final boolean writeHeader) {
50          super(loggerContext, os, fileName, false, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
51          this.randomAccessFile = file;
52          this.advertiseURI = advertiseURI;
53          this.isEndOfBatch.set(Boolean.FALSE);
54      }
55  
56      /**
57       * Returns the RandomAccessFileManager.
58       *
59       * @param fileName The name of the file to manage.
60       * @param append true if the file should be appended to, false if it should
61       *            be overwritten.
62       * @param isFlush true if the contents should be flushed to disk on every
63       *            write
64       * @param bufferSize The buffer size.
65       * @param advertiseURI the URI to use when advertising the file
66       * @param layout The layout.
67       * @param configuration The configuration.
68       * @return A RandomAccessFileManager for the File.
69       */
70      public static RandomAccessFileManager getFileManager(final String fileName, final boolean append,
71              final boolean isFlush, final int bufferSize, final String advertiseURI,
72              final Layout<? extends Serializable> layout, final Configuration configuration) {
73          return (RandomAccessFileManager) getManager(fileName, new FactoryData(append,
74                  isFlush, bufferSize, advertiseURI, layout, configuration), FACTORY);
75      }
76  
77      public Boolean isEndOfBatch() {
78          return isEndOfBatch.get();
79      }
80  
81      public void setEndOfBatch(final boolean endOfBatch) {
82          this.isEndOfBatch.set(Boolean.valueOf(endOfBatch));
83      }
84  
85      @Override
86      protected void writeToDestination(final byte[] bytes, final int offset, final int length) {
87          try {
88              randomAccessFile.write(bytes, offset, length);
89          } catch (final IOException ex) {
90              final String msg = "Error writing to RandomAccessFile " + getName();
91              throw new AppenderLoggingException(msg, ex);
92          }
93      }
94  
95      @Override
96      public synchronized void flush() {
97          flushBuffer(byteBuffer);
98      }
99  
100     @Override
101     public synchronized boolean closeOutputStream() {
102         flush();
103         try {
104             randomAccessFile.close();
105             return true;
106         } catch (final IOException ex) {
107             logError("Unable to close RandomAccessFile", ex);
108             return false;
109         }
110     }
111 
112     /**
113      * Returns the name of the File being managed.
114      *
115      * @return The name of the File being managed.
116      */
117     public String getFileName() {
118         return getName();
119     }
120 
121     /**
122      * Returns the buffer capacity.
123      * @return the buffer size
124      */
125     public int getBufferSize() {
126         return byteBuffer.capacity();
127     }
128 
129     /**
130      * Gets this FileManager's content format specified by:
131      * <p>
132      * Key: "fileURI" Value: provided "advertiseURI" param.
133      * </p>
134      *
135      * @return Map of content format keys supporting FileManager
136      */
137     @Override
138     public Map<String, String> getContentFormat() {
139         final Map<String, String> result = new HashMap<>(
140                 super.getContentFormat());
141         result.put("fileURI", advertiseURI);
142         return result;
143     }
144 
145     /**
146      * Factory Data.
147      */
148     private static class FactoryData extends ConfigurationFactoryData {
149         private final boolean append;
150         private final boolean immediateFlush;
151         private final int bufferSize;
152         private final String advertiseURI;
153         private final Layout<? extends Serializable> layout;
154 
155         /**
156          * Constructor.
157          *
158          * @param append Append status.
159          * @param bufferSize size of the buffer
160          * @param configuration The configuration.
161          */
162         public FactoryData(final boolean append, final boolean immediateFlush, final int bufferSize,
163                 final String advertiseURI, final Layout<? extends Serializable> layout, final Configuration configuration) {
164             super(configuration);
165             this.append = append;
166             this.immediateFlush = immediateFlush;
167             this.bufferSize = bufferSize;
168             this.advertiseURI = advertiseURI;
169             this.layout = layout;
170         }
171     }
172 
173     /**
174      * Factory to create a RandomAccessFileManager.
175      */
176     private static class RandomAccessFileManagerFactory implements
177             ManagerFactory<RandomAccessFileManager, FactoryData> {
178 
179         /**
180          * Create a RandomAccessFileManager.
181          *
182          * @param name The name of the File.
183          * @param data The FactoryData
184          * @return The RandomAccessFileManager for the File.
185          */
186         @Override
187         public RandomAccessFileManager createManager(final String name, final FactoryData data) {
188             final File file = new File(name);
189             final File parent = file.getParentFile();
190             if (null != parent && !parent.exists()) {
191                 parent.mkdirs();
192             }
193             if (!data.append) {
194                 file.delete();
195             }
196 
197             final boolean writeHeader = !data.append || !file.exists();
198             final OutputStream os = NullOutputStream.getInstance();
199             RandomAccessFile raf;
200             try {
201                 raf = new RandomAccessFile(name, "rw");
202                 if (data.append) {
203                     raf.seek(raf.length());
204                 } else {
205                     raf.setLength(0);
206                 }
207                 return new RandomAccessFileManager(data.getLoggerContext(), raf, name,
208                         os, data.bufferSize, data.advertiseURI, data.layout, writeHeader);
209             } catch (final Exception ex) {
210                 LOGGER.error("RandomAccessFileManager (" + name + ") " + ex, ex);
211             }
212             return null;
213         }
214     }
215 
216 }