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; 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; 025import java.util.HashMap; 026import java.util.Map; 027 028import org.apache.logging.log4j.core.Layout; 029import org.apache.logging.log4j.core.util.NullOutputStream; 030 031/** 032 * Extends OutputStreamManager but instead of using a buffered output stream, 033 * this class uses a {@code ByteBuffer} and a {@code RandomAccessFile} to do the 034 * I/O. 035 */ 036public class RandomAccessFileManager extends OutputStreamManager { 037 static final int DEFAULT_BUFFER_SIZE = 256 * 1024; 038 039 private static final RandomAccessFileManagerFactory FACTORY = new RandomAccessFileManagerFactory(); 040 041 private final boolean isImmediateFlush; 042 private final String advertiseURI; 043 private final RandomAccessFile randomAccessFile; 044 private final ByteBuffer buffer; 045 private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<Boolean>(); 046 047 protected RandomAccessFileManager(final RandomAccessFile file, 048 final String fileName, final OutputStream os, 049 final boolean immediateFlush, final int bufferSize, 050 final String advertiseURI, final Layout<? extends Serializable> layout) { 051 super(os, fileName, layout); 052 this.isImmediateFlush = immediateFlush; 053 this.randomAccessFile = file; 054 this.advertiseURI = advertiseURI; 055 this.isEndOfBatch.set(Boolean.FALSE); 056 this.buffer = ByteBuffer.allocate(bufferSize); 057 } 058 059 /** 060 * Returns the RandomAccessFileManager. 061 * 062 * @param fileName The name of the file to manage. 063 * @param append true if the file should be appended to, false if it should 064 * be overwritten. 065 * @param isFlush true if the contents should be flushed to disk on every 066 * write 067 * @param bufferSize The buffer size. 068 * @param advertiseURI the URI to use when advertising the file 069 * @param layout The layout. 070 * @return A RandomAccessFileManager for the File. 071 */ 072 public static RandomAccessFileManager getFileManager(final String fileName, final boolean append, 073 final boolean isFlush, final int bufferSize, final String advertiseURI, 074 final Layout<? extends Serializable> layout) { 075 return (RandomAccessFileManager) getManager(fileName, new FactoryData(append, 076 isFlush, bufferSize, advertiseURI, layout), FACTORY); 077 } 078 079 public Boolean isEndOfBatch() { 080 return isEndOfBatch.get(); 081 } 082 083 public void setEndOfBatch(final boolean isEndOfBatch) { 084 this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch)); 085 } 086 087 @Override 088 protected synchronized void write(final byte[] bytes, int offset, int length) { 089 super.write(bytes, offset, length); // writes to dummy output stream 090 091 int chunk = 0; 092 do { 093 if (length > buffer.remaining()) { 094 flush(); 095 } 096 chunk = Math.min(length, buffer.remaining()); 097 buffer.put(bytes, offset, chunk); 098 offset += chunk; 099 length -= chunk; 100 } while (length > 0); 101 102 if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) { 103 flush(); 104 } 105 } 106 107 @Override 108 public synchronized void flush() { 109 buffer.flip(); 110 try { 111 randomAccessFile.write(buffer.array(), 0, buffer.limit()); 112 } catch (final IOException ex) { 113 final String msg = "Error writing to RandomAccessFile " + getName(); 114 throw new AppenderLoggingException(msg, ex); 115 } 116 buffer.clear(); 117 } 118 119 @Override 120 public synchronized void close() { 121 flush(); 122 try { 123 randomAccessFile.close(); 124 } catch (final IOException ex) { 125 LOGGER.error("Unable to close RandomAccessFile " + getName() + ". " 126 + ex); 127 } 128 } 129 130 /** 131 * Returns the name of the File being managed. 132 * 133 * @return The name of the File being managed. 134 */ 135 public String getFileName() { 136 return getName(); 137 } 138 139 /** 140 * Returns the buffer capacity. 141 * @return the buffer size 142 */ 143 public int getBufferSize() { 144 return buffer.capacity(); 145 } 146 147 /** 148 * Gets this FileManager's content format specified by: 149 * <p> 150 * Key: "fileURI" Value: provided "advertiseURI" param. 151 * </p> 152 * 153 * @return Map of content format keys supporting FileManager 154 */ 155 @Override 156 public Map<String, String> getContentFormat() { 157 final Map<String, String> result = new HashMap<String, String>( 158 super.getContentFormat()); 159 result.put("fileURI", advertiseURI); 160 return result; 161 } 162 163 /** 164 * Factory Data. 165 */ 166 private static class FactoryData { 167 private final boolean append; 168 private final boolean immediateFlush; 169 private final int bufferSize; 170 private final String advertiseURI; 171 private final Layout<? extends Serializable> layout; 172 173 /** 174 * Constructor. 175 * 176 * @param append Append status. 177 * @param bufferSize TODO 178 */ 179 public FactoryData(final boolean append, final boolean immediateFlush, 180 final int bufferSize, final String advertiseURI, final Layout<? extends Serializable> layout) { 181 this.append = append; 182 this.immediateFlush = immediateFlush; 183 this.bufferSize = bufferSize; 184 this.advertiseURI = advertiseURI; 185 this.layout = layout; 186 } 187 } 188 189 /** 190 * Factory to create a RandomAccessFileManager. 191 */ 192 private static class RandomAccessFileManagerFactory implements 193 ManagerFactory<RandomAccessFileManager, FactoryData> { 194 195 /** 196 * Create a RandomAccessFileManager. 197 * 198 * @param name The name of the File. 199 * @param data The FactoryData 200 * @return The RandomAccessFileManager for the File. 201 */ 202 @Override 203 public RandomAccessFileManager createManager(final String name, final FactoryData data) { 204 final File file = new File(name); 205 final File parent = file.getParentFile(); 206 if (null != parent && !parent.exists()) { 207 parent.mkdirs(); 208 } 209 if (!data.append) { 210 file.delete(); 211 } 212 213 final OutputStream os = NullOutputStream.NULL_OUTPUT_STREAM; 214 RandomAccessFile raf; 215 try { 216 raf = new RandomAccessFile(name, "rw"); 217 if (data.append) { 218 raf.seek(raf.length()); 219 } else { 220 raf.setLength(0); 221 } 222 return new RandomAccessFileManager(raf, name, os, data.immediateFlush, 223 data.bufferSize, data.advertiseURI, data.layout); 224 } catch (final Exception ex) { 225 LOGGER.error("RandomAccessFileManager (" + name + ") " + ex); 226 } 227 return null; 228 } 229 } 230 231}