001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j.core.appender.rolling;
018
019 import java.io.File;
020 import java.io.IOException;
021 import java.io.OutputStream;
022 import java.io.RandomAccessFile;
023 import java.io.Serializable;
024 import java.nio.ByteBuffer;
025
026 import org.apache.logging.log4j.core.Layout;
027 import org.apache.logging.log4j.core.appender.AppenderLoggingException;
028 import org.apache.logging.log4j.core.appender.ManagerFactory;
029 import org.apache.logging.log4j.core.util.NullOutputStream;
030
031 /**
032 * Extends RollingFileManager but instead of using a buffered output stream,
033 * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the
034 * I/O.
035 */
036 public class RollingRandomAccessFileManager extends RollingFileManager {
037 /**
038 * The default buffer size
039 */
040 public static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
041
042 private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory();
043
044 private final boolean isImmediateFlush;
045 private RandomAccessFile randomAccessFile;
046 private final ByteBuffer buffer;
047 private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>();
048
049 public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName,
050 final String pattern, final OutputStream os, final boolean append,
051 final boolean immediateFlush, final int bufferSize, final long size, final long time,
052 final TriggeringPolicy policy, final RolloverStrategy strategy,
053 final String advertiseURI, final Layout<? extends Serializable> layout) {
054 super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize);
055 this.isImmediateFlush = immediateFlush;
056 this.randomAccessFile = raf;
057 isEndOfBatch.set(Boolean.FALSE);
058 this.buffer = ByteBuffer.allocate(bufferSize);
059 writeHeader();
060 }
061
062 /**
063 * Writes the layout's header to the file if it exists.
064 */
065 private void writeHeader() {
066 if (layout == null) {
067 return;
068 }
069 final byte[] header = layout.getHeader();
070 if (header == null) {
071 return;
072 }
073 try {
074 // write to the file, not to the buffer: the buffer may not be empty
075 randomAccessFile.write(header, 0, header.length);
076 } catch (final IOException ioe) {
077 LOGGER.error("Unable to write header", ioe);
078 }
079 }
080
081 public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName,
082 final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize,
083 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
084 final Layout<? extends Serializable> layout) {
085 return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend,
086 immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY);
087 }
088
089 public Boolean isEndOfBatch() {
090 return isEndOfBatch.get();
091 }
092
093 public void setEndOfBatch(final boolean isEndOfBatch) {
094 this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch));
095 }
096
097 @Override
098 protected synchronized void write(final byte[] bytes, int offset, int length) {
099 super.write(bytes, offset, length); // writes to dummy output stream, needed to track file size
100
101 int chunk = 0;
102 do {
103 if (length > buffer.remaining()) {
104 flush();
105 }
106 chunk = Math.min(length, buffer.remaining());
107 buffer.put(bytes, offset, chunk);
108 offset += chunk;
109 length -= chunk;
110 } while (length > 0);
111
112 if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) {
113 flush();
114 }
115 }
116
117 @Override
118 protected void createFileAfterRollover() throws IOException {
119 this.randomAccessFile = new RandomAccessFile(getFileName(), "rw");
120 if (isAppend()) {
121 randomAccessFile.seek(randomAccessFile.length());
122 }
123 writeHeader();
124 }
125
126 @Override
127 public synchronized void flush() {
128 buffer.flip();
129 try {
130 randomAccessFile.write(buffer.array(), 0, buffer.limit());
131 } catch (final IOException ex) {
132 final String msg = "Error writing to RandomAccessFile " + getName();
133 throw new AppenderLoggingException(msg, ex);
134 }
135 buffer.clear();
136 }
137
138 @Override
139 public synchronized void close() {
140 flush();
141 try {
142 randomAccessFile.close();
143 } catch (final IOException ex) {
144 LOGGER.error("Unable to close RandomAccessFile " + getName() + ". "
145 + ex);
146 }
147 }
148
149 /**
150 * Returns the buffer capacity.
151 * @return the buffer size
152 */
153 @Override
154 public int getBufferSize() {
155 return buffer.capacity();
156 }
157
158 /**
159 * Factory to create a RollingRandomAccessFileManager.
160 */
161 private static class RollingRandomAccessFileManagerFactory implements ManagerFactory<RollingRandomAccessFileManager, FactoryData> {
162
163 /**
164 * Create the RollingRandomAccessFileManager.
165 *
166 * @param name The name of the entity to manage.
167 * @param data The data required to create the entity.
168 * @return a RollingFileManager.
169 */
170 @Override
171 public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
172 final File file = new File(name);
173 final File parent = file.getParentFile();
174 if (null != parent && !parent.exists()) {
175 parent.mkdirs();
176 }
177
178 if (!data.append) {
179 file.delete();
180 }
181 final long size = data.append ? file.length() : 0;
182 final long time = file.exists() ? file.lastModified() : System.currentTimeMillis();
183
184 RandomAccessFile raf = null;
185 try {
186 raf = new RandomAccessFile(name, "rw");
187 if (data.append) {
188 final long length = raf.length();
189 LOGGER.trace("RandomAccessFile {} seek to {}", name, length);
190 raf.seek(length);
191 } else {
192 LOGGER.trace("RandomAccessFile {} set length to 0", name);
193 raf.setLength(0);
194 }
195 return new RollingRandomAccessFileManager(raf, name, data.pattern, NullOutputStream.NULL_OUTPUT_STREAM,
196 data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy,
197 data.advertiseURI, data.layout);
198 } catch (final IOException ex) {
199 LOGGER.error("Cannot access RandomAccessFile {}) " + ex);
200 if (raf != null) {
201 try {
202 raf.close();
203 } catch (final IOException e) {
204 LOGGER.error("Cannot close RandomAccessFile {}", name, e);
205 }
206 }
207 }
208 return null;
209 }
210 }
211
212 /**
213 * Factory data.
214 */
215 private static class FactoryData {
216 private final String pattern;
217 private final boolean append;
218 private final boolean immediateFlush;
219 private final int bufferSize;
220 private final TriggeringPolicy policy;
221 private final RolloverStrategy strategy;
222 private final String advertiseURI;
223 private final Layout<? extends Serializable> layout;
224
225 /**
226 * Create the data for the factory.
227 *
228 * @param pattern The pattern.
229 * @param append The append flag.
230 * @param immediateFlush
231 * @param bufferSize
232 * @param policy
233 * @param strategy
234 * @param advertiseURI
235 * @param layout
236 */
237 public FactoryData(final String pattern, final boolean append, final boolean immediateFlush,
238 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy,
239 final String advertiseURI, final Layout<? extends Serializable> layout) {
240 this.pattern = pattern;
241 this.append = append;
242 this.immediateFlush = immediateFlush;
243 this.bufferSize = bufferSize;
244 this.policy = policy;
245 this.strategy = strategy;
246 this.advertiseURI = advertiseURI;
247 this.layout = layout;
248 }
249 }
250
251 }