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.FileNotFoundException; 021 import java.io.IOException; 022 import java.io.OutputStream; 023 import java.io.RandomAccessFile; 024 import java.nio.ByteBuffer; 025 026 import org.apache.logging.log4j.core.Layout; 027 import org.apache.logging.log4j.core.appender.AppenderRuntimeException; 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 FastRollingFileManager extends RollingFileManager { 036 private static final int DEFAULT_BUFFER_SIZE = 256 * 1024; 037 038 private static final FastRollingFileManagerFactory FACTORY = new FastRollingFileManagerFactory(); 039 040 private final boolean isImmediateFlush; 041 private RandomAccessFile randomAccessFile; 042 private final ByteBuffer buffer; 043 private ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>(); 044 045 public FastRollingFileManager(final RandomAccessFile raf, final String fileName, 046 final String pattern, final OutputStream os, final boolean append, 047 final boolean immediateFlush, final long size, final long time, 048 final TriggeringPolicy policy, final RolloverStrategy strategy, 049 final String advertiseURI, final Layout layout) { 050 super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout); 051 this.isImmediateFlush = immediateFlush; 052 this.randomAccessFile = raf; 053 isEndOfBatch.set(Boolean.FALSE); 054 055 // TODO make buffer size configurable? 056 buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); 057 } 058 059 public static FastRollingFileManager getFastRollingFileManager(final String fileName, final String filePattern, 060 final boolean isAppend, final boolean immediateFlush, final TriggeringPolicy policy, 061 final RolloverStrategy strategy, final String advertiseURI, final Layout layout) { 062 return (FastRollingFileManager) getManager(fileName, new FactoryData(filePattern, isAppend, immediateFlush, 063 policy, strategy, advertiseURI, layout), FACTORY); 064 } 065 066 public Boolean isEndOfBatch() { 067 return isEndOfBatch.get(); 068 } 069 070 public void setEndOfBatch(boolean isEndOfBatch) { 071 this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch)); 072 } 073 074 @Override 075 protected synchronized void write(byte[] bytes, int offset, int length) { 076 super.write(bytes, offset, length); // writes to dummy output stream 077 078 if (length > buffer.remaining()) { 079 flush(); 080 } 081 buffer.put(bytes, offset, length); 082 if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) { 083 flush(); 084 } 085 } 086 087 @Override 088 protected void createFileAfterRollover() throws IOException { 089 this.randomAccessFile = new RandomAccessFile(getFileName(), "rw"); 090 if (isAppend()) { 091 randomAccessFile.seek(randomAccessFile.length()); 092 } 093 } 094 095 @Override 096 public void flush() { 097 buffer.flip(); 098 try { 099 randomAccessFile.write(buffer.array(), 0, buffer.limit()); 100 } catch (IOException ex) { 101 String msg = "Error writing to RandomAccessFile " + getName(); 102 throw new AppenderRuntimeException(msg, ex); 103 } 104 buffer.clear(); 105 } 106 107 @Override 108 public void close() { 109 flush(); 110 try { 111 randomAccessFile.close(); 112 } catch (final IOException ex) { 113 LOGGER.error("Unable to close RandomAccessFile " + getName() + ". " 114 + ex); 115 } 116 } 117 118 /** 119 * Factory to create a FastRollingFileManager. 120 */ 121 private static class FastRollingFileManagerFactory implements ManagerFactory<FastRollingFileManager, FactoryData> { 122 123 /** 124 * Create the FastRollingFileManager. 125 * 126 * @param name The name of the entity to manage. 127 * @param data The data required to create the entity. 128 * @return a RollingFileManager. 129 */ 130 @Override 131 public FastRollingFileManager createManager(String name, FactoryData data) { 132 File file = new File(name); 133 final File parent = file.getParentFile(); 134 if (null != parent && !parent.exists()) { 135 parent.mkdirs(); 136 } 137 if (!data.append) { 138 file.delete(); 139 } 140 long size = data.append ? file.length() : 0; 141 long time = file.lastModified(); 142 143 RandomAccessFile raf; 144 try { 145 raf = new RandomAccessFile(name, "rw"); 146 return new FastRollingFileManager(raf, name, data.pattern, new DummyOutputStream(), data.append, 147 data.immediateFlush, size, time, data.policy, data.strategy, data.advertiseURI, data.layout); 148 } catch (FileNotFoundException ex) { 149 LOGGER.error("FastRollingFileManager (" + name + ") " + ex); 150 } 151 return null; 152 } 153 } 154 155 /** {@code OutputStream} subclass that does not write anything. */ 156 private static class DummyOutputStream extends OutputStream { 157 @Override 158 public void write(int b) throws IOException { 159 } 160 161 @Override 162 public void write(byte[] b, int off, int len) throws IOException { 163 } 164 } 165 166 /** 167 * Factory data. 168 */ 169 private static class FactoryData { 170 private final String pattern; 171 private final boolean append; 172 private final boolean immediateFlush; 173 private final TriggeringPolicy policy; 174 private final RolloverStrategy strategy; 175 private final String advertiseURI; 176 private final Layout layout; 177 178 /** 179 * Create the data for the factory. 180 * 181 * @param pattern The pattern. 182 * @param append The append flag. 183 * @param immediateFlush 184 */ 185 public FactoryData(final String pattern, final boolean append, final boolean immediateFlush, 186 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 187 final Layout layout) { 188 this.pattern = pattern; 189 this.append = append; 190 this.immediateFlush = immediateFlush; 191 this.policy = policy; 192 this.strategy = strategy; 193 this.advertiseURI = advertiseURI; 194 this.layout = layout; 195 } 196 } 197 198 }