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