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.rolling;
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  
26  import org.apache.logging.log4j.core.Layout;
27  import org.apache.logging.log4j.core.appender.AppenderLoggingException;
28  import org.apache.logging.log4j.core.appender.ManagerFactory;
29  
30  /**
31   * Extends RollingFileManager but instead of using a buffered output stream,
32   * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
33   * I/O.
34   */
35  public class RollingRandomAccessFileManager extends RollingFileManager {
36      static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
37  
38      private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
39  
40      private final boolean isImmediateFlush;
41      private RandomAccessFile randomAccessFile;
42      private final ByteBuffer buffer;
43      private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
44  
45      public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName,
46              final String pattern, final OutputStream os, final boolean append,
47              final boolean immediateFlush, final long size, final long time,
48              final TriggeringPolicy policy, final RolloverStrategy strategy,
49              final String advertiseURI, final Layout<? extends Serializable> layout) {
50          super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout);
51          this.isImmediateFlush = immediateFlush;
52          this.randomAccessFile = raf;
53          isEndOfBatch.set(Boolean.FALSE);
54  
55          // TODO make buffer size configurable?
56          buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
57      }
58  
59      public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName, final String filePattern,
60              final boolean isAppend, final boolean immediateFlush, final TriggeringPolicy policy,
61              final RolloverStrategy strategy, final String advertiseURI, final Layout<? extends Serializable> layout) {
62          return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend, immediateFlush,
63              policy, strategy, advertiseURI, layout), FACTORY);
64      }
65  
66      public Boolean isEndOfBatch() {
67          return isEndOfBatch.get();
68      }
69  
70      public void setEndOfBatch(final boolean isEndOfBatch) {
71          this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
72      }
73  
74      @Override
75      protected synchronized void write(final byte[] bytes, int offset, int length) {
76          super.write(bytes, offset, length); // writes to dummy output stream
77  
78          int chunk = 0;
79          do {
80              if (length > buffer.remaining()) {
81                  flush();
82              }
83              chunk = Math.min(length, buffer.remaining());
84              buffer.put(bytes, offset, chunk);
85              offset += chunk;
86              length -= chunk;
87          } while (length > 0);
88  
89          if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) {
90              flush();
91          }
92      }
93  
94      @Override
95      protected void createFileAfterRollover() throws IOException {
96          this.randomAccessFile = new RandomAccessFile(getFileName(), "rw");
97          if (isAppend()) {
98              randomAccessFile.seek(randomAccessFile.length());
99          }
100     }
101 
102     @Override
103     public synchronized void flush() {
104         buffer.flip();
105         try {
106             randomAccessFile.write(buffer.array(), 0, buffer.limit());
107         } catch (final IOException ex) {
108             final String msg = "Error writing to RandomAccessFile " + getName();
109             throw new AppenderLoggingException(msg, ex);
110         }
111         buffer.clear();
112     }
113 
114     @Override
115     public synchronized void close() {
116         flush();
117         try {
118             randomAccessFile.close();
119         } catch (final IOException ex) {
120             LOGGER.error("Unable to close RandomAccessFile " + getName() + ". "
121                     + ex);
122         }
123     }
124 
125     /**
126      * Factory to create a RollingRandomAccessFileManager.
127      */
128     private static class RollingRandomAccessFileManagerFactory implements ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
129 
130         /**
131          * Create the RollingRandomAccessFileManager.
132          *
133          * @param name The name of the entity to manage.
134          * @param data The data required to create the entity.
135          * @return a RollingFileManager.
136          */
137         @Override
138         public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
139             final File file = new File(name);
140             final File parent = file.getParentFile();
141             if (null != parent && !parent.exists()) {
142                 parent.mkdirs();
143             }
144 
145             if (!data.append) {
146                 file.delete();
147             }
148             final long size = data.append ? file.length() : 0;
149             final long time = file.exists() ? file.lastModified() : System.currentTimeMillis();
150 
151             RandomAccessFile raf = null;
152             try {
153                 raf = new RandomAccessFile(name, "rw");
154                 if (data.append) {
155                     final long length = raf.length();
156                     LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
157                     raf.seek(length);
158                 } else {
159                     LOGGER.trace("RandomAccessFile {} set length to 0", name);
160                     raf.setLength(0);
161                 }
162                 return new RollingRandomAccessFileManager(raf, name, data.pattern, new DummyOutputStream(), data.append,
163                         data.immediateFlush, size, time, data.policy, data.strategy, data.advertiseURI, data.layout);
164             } catch (final IOException ex) {
165                 LOGGER.error("Cannot access RandomAccessFile {}) " + ex);
166                 if (raf != null) {
167                     try {
168                         raf.close();
169                     } catch (IOException e) {
170                         LOGGER.error("Cannot close RandomAccessFile {}", name, e);
171                     }
172                 }
173             }
174             return null;
175         }
176     }
177 
178     /** {@code OutputStream} subclass that does not write anything. */
179     static class DummyOutputStream extends OutputStream {
180         @Override
181         public void write(final int b) throws IOException {
182         }
183 
184         @Override
185         public void write(final byte[] b, final int off, final int len) throws IOException {
186         }
187     }
188 
189     /**
190      * Factory data.
191      */
192     private static class FactoryData {
193         private final String pattern;
194         private final boolean append;
195         private final boolean immediateFlush;
196         private final TriggeringPolicy policy;
197         private final RolloverStrategy strategy;
198         private final String advertiseURI;
199         private final Layout<? extends Serializable> layout;
200 
201         /**
202          * Create the data for the factory.
203          *
204          * @param pattern The pattern.
205          * @param append The append flag.
206          * @param immediateFlush
207          */
208         public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
209                            final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
210                            final Layout<? extends Serializable> layout) {
211             this.pattern = pattern;
212             this.append = append;
213             this.immediateFlush = immediateFlush;
214             this.policy = policy;
215             this.strategy = strategy;
216             this.advertiseURI = advertiseURI;
217             this.layout = layout;
218         }
219     }
220 
221 }