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