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