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<>(); 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, final boolean writeHeader) { 054 super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize, 055 writeHeader); 056 this.isImmediateFlush = immediateFlush; 057 this.randomAccessFile = raf; 058 isEndOfBatch.set(Boolean.FALSE); 059 this.buffer = ByteBuffer.allocate(bufferSize); 060 writeHeader(); 061 } 062 063 /** 064 * Writes the layout's header to the file if it exists. 065 */ 066 private void writeHeader() { 067 if (layout == null) { 068 return; 069 } 070 final byte[] header = layout.getHeader(); 071 if (header == null) { 072 return; 073 } 074 try { 075 // write to the file, not to the buffer: the buffer may not be empty 076 randomAccessFile.write(header, 0, header.length); 077 } catch (final IOException ioe) { 078 LOGGER.error("Unable to write header", ioe); 079 } 080 } 081 082 public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName, 083 final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize, 084 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 085 final Layout<? extends Serializable> layout) { 086 return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend, 087 immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY); 088 } 089 090 public Boolean isEndOfBatch() { 091 return isEndOfBatch.get(); 092 } 093 094 public void setEndOfBatch(final boolean isEndOfBatch) { 095 this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch)); 096 } 097 098 @Override 099 protected synchronized void write(final byte[] bytes, int offset, int length) { 100 super.write(bytes, offset, length); // writes to dummy output stream, needed to track file size 101 102 int chunk = 0; 103 do { 104 if (length > buffer.remaining()) { 105 flush(); 106 } 107 chunk = Math.min(length, buffer.remaining()); 108 buffer.put(bytes, offset, chunk); 109 offset += chunk; 110 length -= chunk; 111 } while (length > 0); 112 113 if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) { 114 flush(); 115 } 116 } 117 118 @Override 119 protected void createFileAfterRollover() throws IOException { 120 this.randomAccessFile = new RandomAccessFile(getFileName(), "rw"); 121 if (isAppend()) { 122 randomAccessFile.seek(randomAccessFile.length()); 123 } 124 writeHeader(); 125 } 126 127 @Override 128 public synchronized void flush() { 129 buffer.flip(); 130 try { 131 randomAccessFile.write(buffer.array(), 0, buffer.limit()); 132 } catch (final IOException ex) { 133 final String msg = "Error writing to RandomAccessFile " + getName(); 134 throw new AppenderLoggingException(msg, ex); 135 } 136 buffer.clear(); 137 } 138 139 @Override 140 public synchronized void close() { 141 flush(); 142 try { 143 randomAccessFile.close(); 144 } catch (final IOException ex) { 145 LOGGER.error("Unable to close RandomAccessFile " + getName() + ". " 146 + ex); 147 } 148 } 149 150 /** 151 * Returns the buffer capacity. 152 * @return the buffer size 153 */ 154 @Override 155 public int getBufferSize() { 156 return buffer.capacity(); 157 } 158 159 /** 160 * Factory to create a RollingRandomAccessFileManager. 161 */ 162 private static class RollingRandomAccessFileManagerFactory implements ManagerFactory<RollingRandomAccessFileManager, FactoryData> { 163 164 /** 165 * Create the RollingRandomAccessFileManager. 166 * 167 * @param name The name of the entity to manage. 168 * @param data The data required to create the entity. 169 * @return a RollingFileManager. 170 */ 171 @Override 172 public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { 173 final File file = new File(name); 174 final File parent = file.getParentFile(); 175 if (null != parent && !parent.exists()) { 176 parent.mkdirs(); 177 } 178 179 if (!data.append) { 180 file.delete(); 181 } 182 final long size = data.append ? file.length() : 0; 183 final long time = file.exists() ? file.lastModified() : System.currentTimeMillis(); 184 185 final boolean writeHeader = !data.append || !file.exists(); 186 RandomAccessFile raf = null; 187 try { 188 raf = new RandomAccessFile(name, "rw"); 189 if (data.append) { 190 final long length = raf.length(); 191 LOGGER.trace("RandomAccessFile {} seek to {}", name, length); 192 raf.seek(length); 193 } else { 194 LOGGER.trace("RandomAccessFile {} set length to 0", name); 195 raf.setLength(0); 196 } 197 return new RollingRandomAccessFileManager(raf, name, data.pattern, NullOutputStream.NULL_OUTPUT_STREAM, 198 data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy, 199 data.advertiseURI, data.layout, writeHeader); 200 } catch (final IOException ex) { 201 LOGGER.error("Cannot access RandomAccessFile {}) " + ex); 202 if (raf != null) { 203 try { 204 raf.close(); 205 } catch (final IOException e) { 206 LOGGER.error("Cannot close RandomAccessFile {}", name, e); 207 } 208 } 209 } 210 return null; 211 } 212 } 213 214 /** 215 * Factory data. 216 */ 217 private static class FactoryData { 218 private final String pattern; 219 private final boolean append; 220 private final boolean immediateFlush; 221 private final int bufferSize; 222 private final TriggeringPolicy policy; 223 private final RolloverStrategy strategy; 224 private final String advertiseURI; 225 private final Layout<? extends Serializable> layout; 226 227 /** 228 * Create the data for the factory. 229 * 230 * @param pattern The pattern. 231 * @param append The append flag. 232 * @param immediateFlush 233 * @param bufferSize 234 * @param policy 235 * @param strategy 236 * @param advertiseURI 237 * @param layout 238 */ 239 public FactoryData(final String pattern, final boolean append, final boolean immediateFlush, 240 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy, 241 final String advertiseURI, final Layout<? extends Serializable> layout) { 242 this.pattern = pattern; 243 this.append = append; 244 this.immediateFlush = immediateFlush; 245 this.bufferSize = bufferSize; 246 this.policy = policy; 247 this.strategy = strategy; 248 this.advertiseURI = advertiseURI; 249 this.layout = layout; 250 } 251 } 252 253}