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 * Gets this FileManager's content format specified by: 159 * <p> 160 * Key: "fileURI" Value: provided "advertiseURI" param. 161 * </p> 162 * 163 * @return Map of content format keys supporting FileManager 164 */ 165 @Override 166 public Map<String, String> getContentFormat() { 167 final Map<String, String> result = new HashMap<String, String>( 168 super.getContentFormat()); 169 result.put("fileURI", advertiseURI); 170 return result; 171 } 172 173 /** 174 * Factory Data. 175 */ 176 private static class FactoryData { 177 private final boolean append; 178 private final boolean immediateFlush; 179 private final int bufferSize; 180 private final String advertiseURI; 181 private final Layout<? extends Serializable> layout; 182 183 /** 184 * Constructor. 185 * 186 * @param append Append status. 187 * @param bufferSize TODO 188 */ 189 public FactoryData(final boolean append, final boolean immediateFlush, 190 final int bufferSize, final String advertiseURI, final Layout<? extends Serializable> layout) { 191 this.append = append; 192 this.immediateFlush = immediateFlush; 193 this.bufferSize = bufferSize; 194 this.advertiseURI = advertiseURI; 195 this.layout = layout; 196 } 197 } 198 199 /** 200 * Factory to create a RandomAccessFileManager. 201 */ 202 private static class RandomAccessFileManagerFactory implements 203 ManagerFactory<RandomAccessFileManager, FactoryData> { 204 205 /** 206 * Create a RandomAccessFileManager. 207 * 208 * @param name The name of the File. 209 * @param data The FactoryData 210 * @return The RandomAccessFileManager for the File. 211 */ 212 @Override 213 public RandomAccessFileManager createManager(final String name, final FactoryData data) { 214 final File file = new File(name); 215 final File parent = file.getParentFile(); 216 if (null != parent && !parent.exists()) { 217 parent.mkdirs(); 218 } 219 if (!data.append) { 220 file.delete(); 221 } 222 223 final OutputStream os = new DummyOutputStream(); 224 RandomAccessFile raf; 225 try { 226 raf = new RandomAccessFile(name, "rw"); 227 if (data.append) { 228 raf.seek(raf.length()); 229 } else { 230 raf.setLength(0); 231 } 232 return new RandomAccessFileManager(raf, name, os, data.immediateFlush, 233 data.bufferSize, data.advertiseURI, data.layout); 234 } catch (final Exception ex) { 235 LOGGER.error("RandomAccessFileManager (" + name + ") " + ex); 236 } 237 return null; 238 } 239 } 240 241 }