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