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 final 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 @Override 153 public int getBufferSize() { 154 return buffer.capacity(); 155 } 156 157 /** 158 * Factory to create a RollingRandomAccessFileManager. 159 */ 160 private static class RollingRandomAccessFileManagerFactory implements ManagerFactory<RollingRandomAccessFileManager, FactoryData> { 161 162 /** 163 * Create the RollingRandomAccessFileManager. 164 * 165 * @param name The name of the entity to manage. 166 * @param data The data required to create the entity. 167 * @return a RollingFileManager. 168 */ 169 @Override 170 public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { 171 final File file = new File(name); 172 final File parent = file.getParentFile(); 173 if (null != parent && !parent.exists()) { 174 parent.mkdirs(); 175 } 176 177 if (!data.append) { 178 file.delete(); 179 } 180 final long size = data.append ? file.length() : 0; 181 final long time = file.exists() ? file.lastModified() : System.currentTimeMillis(); 182 183 RandomAccessFile raf = null; 184 try { 185 raf = new RandomAccessFile(name, "rw"); 186 if (data.append) { 187 final long length = raf.length(); 188 LOGGER.trace("RandomAccessFile {} seek to {}", name, length); 189 raf.seek(length); 190 } else { 191 LOGGER.trace("RandomAccessFile {} set length to 0", name); 192 raf.setLength(0); 193 } 194 return new RollingRandomAccessFileManager(raf, name, data.pattern, new DummyOutputStream(), data.append, 195 data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy, data.advertiseURI, 196 data.layout); 197 } catch (final IOException ex) { 198 LOGGER.error("Cannot access RandomAccessFile {}) " + ex); 199 if (raf != null) { 200 try { 201 raf.close(); 202 } catch (final IOException e) { 203 LOGGER.error("Cannot close RandomAccessFile {}", name, e); 204 } 205 } 206 } 207 return null; 208 } 209 } 210 211 /** {@code OutputStream} subclass that does not write anything. */ 212 static class DummyOutputStream extends OutputStream { 213 @Override 214 public void write(final int b) throws IOException { 215 } 216 217 @Override 218 public void write(final byte[] b, final int off, final int len) throws IOException { 219 } 220 } 221 222 /** 223 * Factory data. 224 */ 225 private static class FactoryData { 226 private final String pattern; 227 private final boolean append; 228 private final boolean immediateFlush; 229 private final int bufferSize; 230 private final TriggeringPolicy policy; 231 private final RolloverStrategy strategy; 232 private final String advertiseURI; 233 private final Layout<? extends Serializable> layout; 234 235 /** 236 * Create the data for the factory. 237 * 238 * @param pattern The pattern. 239 * @param append The append flag. 240 * @param immediateFlush 241 * @param bufferSize 242 * @param policy 243 * @param strategy 244 * @param advertiseURI 245 * @param layout 246 */ 247 public FactoryData(final String pattern, final boolean append, final boolean immediateFlush, 248 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy, 249 final String advertiseURI, final Layout<? extends Serializable> layout) { 250 this.pattern = pattern; 251 this.append = append; 252 this.immediateFlush = immediateFlush; 253 this.bufferSize = bufferSize; 254 this.policy = policy; 255 this.strategy = strategy; 256 this.advertiseURI = advertiseURI; 257 this.layout = layout; 258 } 259 } 260 261 }