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