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