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; 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 */ 035public class RollingRandomAccessFileManager extends RollingFileManager { 036 static final int DEFAULT_BUFFER_SIZE = 256 * 1024; 037 038 private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory(); 039 040 private final boolean isImmediateFlush; 041 private RandomAccessFile randomAccessFile; 042 private final ByteBuffer buffer; 043 private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>(); 044 045 public RollingRandomAccessFileManager(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<? extends Serializable> 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 RollingRandomAccessFileManager getRollingRandomAccessFileManager(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<? extends Serializable> layout) { 062 return (RollingRandomAccessFileManager) 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(final boolean isEndOfBatch) { 071 this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch)); 072 } 073 074 @Override 075 protected synchronized void write(final byte[] bytes, int offset, int length) { 076 super.write(bytes, offset, length); // writes to dummy output stream 077 078 int chunk = 0; 079 do { 080 if (length > buffer.remaining()) { 081 flush(); 082 } 083 chunk = Math.min(length, buffer.remaining()); 084 buffer.put(bytes, offset, chunk); 085 offset += chunk; 086 length -= chunk; 087 } while (length > 0); 088 089 if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) { 090 flush(); 091 } 092 } 093 094 @Override 095 protected void createFileAfterRollover() throws IOException { 096 this.randomAccessFile = new RandomAccessFile(getFileName(), "rw"); 097 if (isAppend()) { 098 randomAccessFile.seek(randomAccessFile.length()); 099 } 100 } 101 102 @Override 103 public synchronized void flush() { 104 buffer.flip(); 105 try { 106 randomAccessFile.write(buffer.array(), 0, buffer.limit()); 107 } catch (final IOException ex) { 108 final String msg = "Error writing to RandomAccessFile " + getName(); 109 throw new AppenderLoggingException(msg, ex); 110 } 111 buffer.clear(); 112 } 113 114 @Override 115 public synchronized void close() { 116 flush(); 117 try { 118 randomAccessFile.close(); 119 } catch (final IOException ex) { 120 LOGGER.error("Unable to close RandomAccessFile " + getName() + ". " 121 + ex); 122 } 123 } 124 125 /** 126 * Factory to create a RollingRandomAccessFileManager. 127 */ 128 private static class RollingRandomAccessFileManagerFactory implements ManagerFactory<RollingRandomAccessFileManager, FactoryData> { 129 130 /** 131 * Create the RollingRandomAccessFileManager. 132 * 133 * @param name The name of the entity to manage. 134 * @param data The data required to create the entity. 135 * @return a RollingFileManager. 136 */ 137 @Override 138 public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { 139 final File file = new File(name); 140 final File parent = file.getParentFile(); 141 if (null != parent && !parent.exists()) { 142 parent.mkdirs(); 143 } 144 145 if (!data.append) { 146 file.delete(); 147 } 148 final long size = data.append ? file.length() : 0; 149 final long time = file.exists() ? file.lastModified() : System.currentTimeMillis(); 150 151 RandomAccessFile raf = null; 152 try { 153 raf = new RandomAccessFile(name, "rw"); 154 if (data.append) { 155 final long length = raf.length(); 156 LOGGER.trace("RandomAccessFile {} seek to {}", name, length); 157 raf.seek(length); 158 } else { 159 LOGGER.trace("RandomAccessFile {} set length to 0", name); 160 raf.setLength(0); 161 } 162 return new RollingRandomAccessFileManager(raf, name, data.pattern, new DummyOutputStream(), data.append, 163 data.immediateFlush, size, time, data.policy, data.strategy, data.advertiseURI, data.layout); 164 } catch (final IOException ex) { 165 LOGGER.error("Cannot access RandomAccessFile {}) " + ex); 166 if (raf != null) { 167 try { 168 raf.close(); 169 } catch (IOException e) { 170 LOGGER.error("Cannot close RandomAccessFile {}", name, e); 171 } 172 } 173 } 174 return null; 175 } 176 } 177 178 /** {@code OutputStream} subclass that does not write anything. */ 179 static class DummyOutputStream extends OutputStream { 180 @Override 181 public void write(final int b) throws IOException { 182 } 183 184 @Override 185 public void write(final byte[] b, final int off, final int len) throws IOException { 186 } 187 } 188 189 /** 190 * Factory data. 191 */ 192 private static class FactoryData { 193 private final String pattern; 194 private final boolean append; 195 private final boolean immediateFlush; 196 private final TriggeringPolicy policy; 197 private final RolloverStrategy strategy; 198 private final String advertiseURI; 199 private final Layout<? extends Serializable> layout; 200 201 /** 202 * Create the data for the factory. 203 * 204 * @param pattern The pattern. 205 * @param append The append flag. 206 * @param immediateFlush 207 */ 208 public FactoryData(final String pattern, final boolean append, final boolean immediateFlush, 209 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 210 final Layout<? extends Serializable> layout) { 211 this.pattern = pattern; 212 this.append = append; 213 this.immediateFlush = immediateFlush; 214 this.policy = policy; 215 this.strategy = strategy; 216 this.advertiseURI = advertiseURI; 217 this.layout = layout; 218 } 219 } 220 221}