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.FileNotFoundException; 021import java.io.FileOutputStream; 022import java.io.IOException; 023import java.io.OutputStream; 024import java.io.Serializable; 025import java.nio.ByteBuffer; 026import java.nio.channels.FileChannel; 027import java.nio.channels.FileLock; 028import java.util.HashMap; 029import java.util.Map; 030 031import org.apache.logging.log4j.core.Layout; 032import org.apache.logging.log4j.core.util.Constants; 033 034 035/** 036 * Manages actual File I/O for File Appenders. 037 */ 038public class FileManager extends OutputStreamManager { 039 040 private static final FileManagerFactory FACTORY = new FileManagerFactory(); 041 042 private final boolean isAppend; 043 private final boolean isLocking; 044 private final String advertiseURI; 045 private final int bufferSize; 046 047 @Deprecated 048 protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking, 049 final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize, 050 final boolean writeHeader) { 051 this(fileName, os, append, locking, advertiseURI, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); 052 } 053 054 /** @since 2.6 */ 055 protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking, 056 final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader, 057 final ByteBuffer buffer) { 058 super(os, fileName, layout, writeHeader, buffer); 059 this.isAppend = append; 060 this.isLocking = locking; 061 this.advertiseURI = advertiseURI; 062 this.bufferSize = buffer.capacity(); 063 } 064 065 /** 066 * Returns the FileManager. 067 * @param fileName The name of the file to manage. 068 * @param append true if the file should be appended to, false if it should be overwritten. 069 * @param locking true if the file should be locked while writing, false otherwise. 070 * @param bufferedIo true if the contents should be buffered as they are written. 071 * @param advertiseUri the URI to use when advertising the file 072 * @param layout The layout 073 * @param bufferSize buffer size for buffered IO 074 * @param immediateFlush true if the contents should be flushed on every write, false otherwise. 075 * @return A FileManager for the File. 076 */ 077 public static FileManager getFileManager(final String fileName, final boolean append, boolean locking, 078 final boolean bufferedIo, final String advertiseUri, final Layout<? extends Serializable> layout, 079 final int bufferSize, final boolean immediateFlush) { 080 081 if (locking && bufferedIo) { 082 locking = false; 083 } 084 return (FileManager) getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize, 085 immediateFlush, advertiseUri, layout), FACTORY); 086 } 087 088 @Override 089 protected synchronized void write(final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { 090 091 if (isLocking) { 092 final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel(); 093 try { 094 /* Lock the whole file. This could be optimized to only lock from the current file 095 position. Note that locking may be advisory on some systems and mandatory on others, 096 so locking just from the current position would allow reading on systems where 097 locking is mandatory. Also, Java 6 will throw an exception if the region of the 098 file is already locked by another FileChannel in the same JVM. Hopefully, that will 099 be avoided since every file should have a single file manager - unless two different 100 files strings are configured that somehow map to the same file.*/ 101 final FileLock lock = channel.lock(0, Long.MAX_VALUE, false); 102 try { 103 super.write(bytes, offset, length, immediateFlush); 104 } finally { 105 lock.release(); 106 } 107 } catch (final IOException ex) { 108 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex); 109 } 110 111 } else { 112 super.write(bytes, offset, length, immediateFlush); 113 } 114 } 115 116 /** 117 * Returns the name of the File being managed. 118 * @return The name of the File being managed. 119 */ 120 public String getFileName() { 121 return getName(); 122 } 123 124 /** 125 * Returns the append status. 126 * @return true if the file will be appended to, false if it is overwritten. 127 */ 128 public boolean isAppend() { 129 return isAppend; 130 } 131 132 /** 133 * Returns the lock status. 134 * @return true if the file will be locked when writing, false otherwise. 135 */ 136 public boolean isLocking() { 137 return isLocking; 138 } 139 140 /** 141 * Returns the buffer size to use if the appender was configured with BufferedIO=true, otherwise returns a negative 142 * number. 143 * @return the buffer size, or a negative number if the output stream is not buffered 144 */ 145 public int getBufferSize() { 146 return bufferSize; 147 } 148 149 /** 150 * FileManager's content format is specified by: <code>Key: "fileURI" Value: provided "advertiseURI" param</code>. 151 * 152 * @return Map of content format keys supporting FileManager 153 */ 154 @Override 155 public Map<String, String> getContentFormat() { 156 final Map<String, String> result = new HashMap<>(super.getContentFormat()); 157 result.put("fileURI", advertiseURI); 158 return result; 159 } 160 161 /** 162 * Factory Data. 163 */ 164 private static class FactoryData { 165 private final boolean append; 166 private final boolean locking; 167 private final boolean bufferedIO; 168 private final int bufferSize; 169 private final boolean immediateFlush; 170 private final String advertiseURI; 171 private final Layout<? extends Serializable> layout; 172 173 /** 174 * Constructor. 175 * @param append Append status. 176 * @param locking Locking status. 177 * @param bufferedIO Buffering flag. 178 * @param bufferSize Buffer size. 179 * @param immediateFlush flush on every write or not 180 * @param advertiseURI the URI to use when advertising the file 181 */ 182 public FactoryData(final boolean append, final boolean locking, final boolean bufferedIO, final int bufferSize, 183 final boolean immediateFlush, final String advertiseURI, final Layout<? extends Serializable> layout) { 184 this.append = append; 185 this.locking = locking; 186 this.bufferedIO = bufferedIO; 187 this.bufferSize = bufferSize; 188 this.immediateFlush = immediateFlush; 189 this.advertiseURI = advertiseURI; 190 this.layout = layout; 191 } 192 } 193 194 /** 195 * Factory to create a FileManager. 196 */ 197 private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> { 198 199 /** 200 * Create a FileManager. 201 * @param name The name of the File. 202 * @param data The FactoryData 203 * @return The FileManager for the File. 204 */ 205 @Override 206 public FileManager createManager(final String name, final FactoryData data) { 207 final File file = new File(name); 208 final File parent = file.getParentFile(); 209 if (null != parent && !parent.exists()) { 210 parent.mkdirs(); 211 } 212 213 final boolean writeHeader = !data.append || !file.exists(); 214 OutputStream os; 215 try { 216 os = new FileOutputStream(name, data.append); 217 final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE; 218 final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]); 219 return new FileManager(name, os, data.append, data.locking, data.advertiseURI, data.layout, 220 writeHeader, buffer); 221 } catch (final FileNotFoundException ex) { 222 LOGGER.error("FileManager (" + name + ") " + ex, ex); 223 } 224 return null; 225 } 226 } 227 228}