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