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