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