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 */ 017package org.apache.logging.log4j.core.appender.rolling; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.OutputStream; 022import java.io.RandomAccessFile; 023import java.io.Serializable; 024import java.nio.ByteBuffer; 025 026import org.apache.logging.log4j.core.Layout; 027import org.apache.logging.log4j.core.appender.AppenderLoggingException; 028import org.apache.logging.log4j.core.appender.ManagerFactory; 029import 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 */ 036public 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}