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