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, this class uses a {@code ByteBuffer} and a 033 * {@code RandomAccessFile} to do the I/O. 034 */ 035public 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<>(); 047 048 public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName, final String pattern, 049 final OutputStream os, final boolean append, final boolean immediateFlush, final int bufferSize, 050 final long size, final long time, final TriggeringPolicy policy, final RolloverStrategy strategy, 051 final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader) { 052 super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize, 053 writeHeader); 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 e) { 076 logError("unable to write header", e); 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 endOfBatch) { 093 this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); 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 e) { 143 logError("unable to close RandomAccessFile", e); 144 } 145 } 146 147 /** 148 * Returns the buffer capacity. 149 * 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 161 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 final boolean writeHeader = !data.append || !file.exists(); 185 RandomAccessFile raf = null; 186 try { 187 raf = new RandomAccessFile(name, "rw"); 188 if (data.append) { 189 final long length = raf.length(); 190 LOGGER.trace("RandomAccessFile {} seek to {}", name, length); 191 raf.seek(length); 192 } else { 193 LOGGER.trace("RandomAccessFile {} set length to 0", name); 194 raf.setLength(0); 195 } 196 return new RollingRandomAccessFileManager(raf, name, data.pattern, NullOutputStream.NULL_OUTPUT_STREAM, 197 data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy, 198 data.advertiseURI, data.layout, writeHeader); 199 } catch (final IOException ex) { 200 LOGGER.error("Cannot access RandomAccessFile {}) " + ex); 201 if (raf != null) { 202 try { 203 raf.close(); 204 } catch (final IOException e) { 205 LOGGER.error("Cannot close RandomAccessFile {}", name, e); 206 } 207 } 208 } 209 return null; 210 } 211 } 212 213 /** 214 * Factory data. 215 */ 216 private static class FactoryData { 217 private final String pattern; 218 private final boolean append; 219 private final boolean immediateFlush; 220 private final int bufferSize; 221 private final TriggeringPolicy policy; 222 private final RolloverStrategy strategy; 223 private final String advertiseURI; 224 private final Layout<? extends Serializable> layout; 225 226 /** 227 * Create the data for the factory. 228 * 229 * @param pattern The pattern. 230 * @param append The append flag. 231 * @param immediateFlush 232 * @param bufferSize 233 * @param policy 234 * @param strategy 235 * @param advertiseURI 236 * @param layout 237 */ 238 public FactoryData(final String pattern, final boolean append, final boolean immediateFlush, 239 final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy, 240 final String advertiseURI, final Layout<? extends Serializable> layout) { 241 this.pattern = pattern; 242 this.append = append; 243 this.immediateFlush = immediateFlush; 244 this.bufferSize = bufferSize; 245 this.policy = policy; 246 this.strategy = strategy; 247 this.advertiseURI = advertiseURI; 248 this.layout = layout; 249 } 250 251 public TriggeringPolicy getTriggeringPolicy() 252 { 253 return this.policy; 254 } 255 256 public RolloverStrategy getRolloverStrategy() 257 { 258 return this.strategy; 259 } 260 } 261 262 @Override 263 public void updateData(final Object data) 264 { 265 final FactoryData factoryData = (FactoryData) data; 266 setRolloverStrategy(factoryData.getRolloverStrategy()); 267 setTriggeringPolicy(factoryData.getTriggeringPolicy()); 268 } 269}