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