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<>(); 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, final boolean writeHeader) { 051 super(os, fileName, layout, writeHeader); 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 endOfBatch) { 084 this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); 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 logError("unable to close RandomAccessFile", ex); 126 } 127 } 128 129 /** 130 * Returns the name of the File being managed. 131 * 132 * @return The name of the File being managed. 133 */ 134 public String getFileName() { 135 return getName(); 136 } 137 138 /** 139 * Returns the buffer capacity. 140 * @return the buffer size 141 */ 142 public int getBufferSize() { 143 return buffer.capacity(); 144 } 145 146 /** 147 * Gets this FileManager's content format specified by: 148 * <p> 149 * Key: "fileURI" Value: provided "advertiseURI" param. 150 * </p> 151 * 152 * @return Map of content format keys supporting FileManager 153 */ 154 @Override 155 public Map<String, String> getContentFormat() { 156 final Map<String, String> result = new HashMap<>( 157 super.getContentFormat()); 158 result.put("fileURI", advertiseURI); 159 return result; 160 } 161 162 /** 163 * Factory Data. 164 */ 165 private static class FactoryData { 166 private final boolean append; 167 private final boolean immediateFlush; 168 private final int bufferSize; 169 private final String advertiseURI; 170 private final Layout<? extends Serializable> layout; 171 172 /** 173 * Constructor. 174 * 175 * @param append Append status. 176 * @param bufferSize TODO 177 */ 178 public FactoryData(final boolean append, final boolean immediateFlush, 179 final int bufferSize, final String advertiseURI, final Layout<? extends Serializable> layout) { 180 this.append = append; 181 this.immediateFlush = immediateFlush; 182 this.bufferSize = bufferSize; 183 this.advertiseURI = advertiseURI; 184 this.layout = layout; 185 } 186 } 187 188 /** 189 * Factory to create a RandomAccessFileManager. 190 */ 191 private static class RandomAccessFileManagerFactory implements 192 ManagerFactory<RandomAccessFileManager, FactoryData> { 193 194 /** 195 * Create a RandomAccessFileManager. 196 * 197 * @param name The name of the File. 198 * @param data The FactoryData 199 * @return The RandomAccessFileManager for the File. 200 */ 201 @Override 202 public RandomAccessFileManager createManager(final String name, final FactoryData data) { 203 final File file = new File(name); 204 final File parent = file.getParentFile(); 205 if (null != parent && !parent.exists()) { 206 parent.mkdirs(); 207 } 208 if (!data.append) { 209 file.delete(); 210 } 211 212 final boolean writeHeader = !data.append || !file.exists(); 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, writeHeader); 224 } catch (final Exception ex) { 225 LOGGER.error("RandomAccessFileManager (" + name + ") " + ex, ex); 226 } 227 return null; 228 } 229 } 230 231}