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