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.FileOutputStream; 021import java.io.IOException; 022import java.io.OutputStream; 023import java.io.Serializable; 024import java.nio.ByteBuffer; 025import java.nio.channels.FileChannel; 026import java.nio.channels.FileLock; 027import java.nio.file.FileSystems; 028import java.nio.file.Path; 029import java.nio.file.Paths; 030import java.nio.file.attribute.FileOwnerAttributeView; 031import java.nio.file.attribute.PosixFileAttributeView; 032import java.nio.file.attribute.PosixFilePermission; 033import java.nio.file.attribute.PosixFilePermissions; 034import java.util.Date; 035import java.util.HashMap; 036import java.util.Map; 037import java.util.Set; 038 039import org.apache.logging.log4j.core.Layout; 040import org.apache.logging.log4j.core.LoggerContext; 041import org.apache.logging.log4j.core.config.Configuration; 042import org.apache.logging.log4j.core.util.Constants; 043import org.apache.logging.log4j.core.util.FileUtils; 044 045 046/** 047 * Manages actual File I/O for File Appenders. 048 */ 049public class FileManager extends OutputStreamManager { 050 051 private static final FileManagerFactory FACTORY = new FileManagerFactory(); 052 053 private final boolean isAppend; 054 private final boolean createOnDemand; 055 private final boolean isLocking; 056 private final String advertiseURI; 057 private final int bufferSize; 058 private final Set<PosixFilePermission> filePermissions; 059 private final String fileOwner; 060 private final String fileGroup; 061 private final boolean attributeViewEnabled; 062 063 /** 064 * @deprecated 065 */ 066 @Deprecated 067 protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking, 068 final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize, 069 final boolean writeHeader) { 070 this(fileName, os, append, locking, advertiseURI, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); 071 } 072 073 /** 074 * @deprecated 075 * @since 2.6 076 */ 077 @Deprecated 078 protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking, 079 final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader, 080 final ByteBuffer buffer) { 081 super(os, fileName, layout, writeHeader, buffer); 082 this.isAppend = append; 083 this.createOnDemand = false; 084 this.isLocking = locking; 085 this.advertiseURI = advertiseURI; 086 this.bufferSize = buffer.capacity(); 087 this.filePermissions = null; 088 this.fileOwner = null; 089 this.fileGroup = null; 090 this.attributeViewEnabled = false; 091 } 092 093 /** 094 * @deprecated 095 * @since 2.7 096 */ 097 @Deprecated 098 protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking, 099 final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout, 100 final boolean writeHeader, final ByteBuffer buffer) { 101 super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer); 102 this.isAppend = append; 103 this.createOnDemand = createOnDemand; 104 this.isLocking = locking; 105 this.advertiseURI = advertiseURI; 106 this.bufferSize = buffer.capacity(); 107 this.filePermissions = null; 108 this.fileOwner = null; 109 this.fileGroup = null; 110 this.attributeViewEnabled = false; 111 } 112 113 /** 114 * @since 2.9 115 */ 116 protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking, 117 final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout, 118 final String filePermissions, final String fileOwner, final String fileGroup, final boolean writeHeader, 119 final ByteBuffer buffer) { 120 super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer); 121 this.isAppend = append; 122 this.createOnDemand = createOnDemand; 123 this.isLocking = locking; 124 this.advertiseURI = advertiseURI; 125 this.bufferSize = buffer.capacity(); 126 127 final Set<String> views = FileSystems.getDefault().supportedFileAttributeViews(); 128 if (views.contains("posix")) { 129 this.filePermissions = filePermissions != null ? PosixFilePermissions.fromString(filePermissions) : null; 130 this.fileGroup = fileGroup; 131 } else { 132 this.filePermissions = null; 133 this.fileGroup = null; 134 if (filePermissions != null) { 135 LOGGER.warn("Posix file attribute permissions defined but it is not supported by this files system."); 136 } 137 if (fileGroup != null) { 138 LOGGER.warn("Posix file attribute group defined but it is not supported by this files system."); 139 } 140 } 141 142 if (views.contains("owner")) { 143 this.fileOwner = fileOwner; 144 } else { 145 this.fileOwner = null; 146 if (fileOwner != null) { 147 LOGGER.warn("Owner file attribute defined but it is not supported by this files system."); 148 } 149 } 150 151 // Supported and defined 152 this.attributeViewEnabled = this.filePermissions != null || this.fileOwner != null || this.fileGroup != null; 153 } 154 155 /** 156 * Returns the FileManager. 157 * @param fileName The name of the file to manage. 158 * @param append true if the file should be appended to, false if it should be overwritten. 159 * @param locking true if the file should be locked while writing, false otherwise. 160 * @param bufferedIo true if the contents should be buffered as they are written. 161 * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.) 162 * @param advertiseUri the URI to use when advertising the file 163 * @param layout The layout 164 * @param bufferSize buffer size for buffered IO 165 * @param filePermissions File permissions 166 * @param fileOwner File owner 167 * @param fileGroup File group 168 * @param configuration The configuration. 169 * @return A FileManager for the File. 170 */ 171 public static FileManager getFileManager(final String fileName, final boolean append, boolean locking, 172 final boolean bufferedIo, final boolean createOnDemand, final String advertiseUri, 173 final Layout<? extends Serializable> layout, 174 final int bufferSize, final String filePermissions, final String fileOwner, final String fileGroup, 175 final Configuration configuration) { 176 177 if (locking && bufferedIo) { 178 locking = false; 179 } 180 return narrow(FileManager.class, getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize, 181 createOnDemand, advertiseUri, layout, filePermissions, fileOwner, fileGroup, configuration), FACTORY)); 182 } 183 184 @Override 185 protected OutputStream createOutputStream() throws IOException { 186 final String filename = getFileName(); 187 LOGGER.debug("Now writing to {} at {}", filename, new Date()); 188 final FileOutputStream fos = new FileOutputStream(filename, isAppend); 189 defineAttributeView(Paths.get(filename)); 190 return fos; 191 } 192 193 protected void defineAttributeView(final Path path) { 194 if (attributeViewEnabled) { 195 try { 196 // FileOutputStream may not create new file on all jvm 197 path.toFile().createNewFile(); 198 199 FileUtils.defineFilePosixAttributeView(path, filePermissions, fileOwner, fileGroup); 200 } catch (final Exception e) { 201 LOGGER.error("Could not define attribute view on path \"{}\" got {}", path, e.getMessage(), e); 202 } 203 } 204 } 205 206 @Override 207 protected synchronized void write(final byte[] bytes, final int offset, final int length, 208 final boolean immediateFlush) { 209 if (isLocking) { 210 try { 211 @SuppressWarnings("resource") 212 final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel(); 213 /* 214 * Lock the whole file. This could be optimized to only lock from the current file position. Note that 215 * locking may be advisory on some systems and mandatory on others, so locking just from the current 216 * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an 217 * exception if the region of the file is already locked by another FileChannel in the same JVM. 218 * Hopefully, that will be avoided since every file should have a single file manager - unless two 219 * different files strings are configured that somehow map to the same file. 220 */ 221 try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) { 222 super.write(bytes, offset, length, immediateFlush); 223 } 224 } catch (final IOException ex) { 225 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex); 226 } 227 } else { 228 super.write(bytes, offset, length, immediateFlush); 229 } 230 } 231 232 /** 233 * Overrides {@link OutputStreamManager#writeToDestination(byte[], int, int)} to add support for file locking. 234 * 235 * @param bytes the array containing data 236 * @param offset from where to write 237 * @param length how many bytes to write 238 * @since 2.8 239 */ 240 @Override 241 protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { 242 if (isLocking) { 243 try { 244 @SuppressWarnings("resource") 245 final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel(); 246 /* 247 * Lock the whole file. This could be optimized to only lock from the current file position. Note that 248 * locking may be advisory on some systems and mandatory on others, so locking just from the current 249 * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an 250 * exception if the region of the file is already locked by another FileChannel in the same JVM. 251 * Hopefully, that will be avoided since every file should have a single file manager - unless two 252 * different files strings are configured that somehow map to the same file. 253 */ 254 try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) { 255 super.writeToDestination(bytes, offset, length); 256 } 257 } catch (final IOException ex) { 258 throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex); 259 } 260 } else { 261 super.writeToDestination(bytes, offset, length); 262 } 263 } 264 265 /** 266 * Returns the name of the File being managed. 267 * @return The name of the File being managed. 268 */ 269 public String getFileName() { 270 return getName(); 271 } 272 /** 273 * Returns the append status. 274 * @return true if the file will be appended to, false if it is overwritten. 275 */ 276 public boolean isAppend() { 277 return isAppend; 278 } 279 280 /** 281 * Returns the lazy-create. 282 * @return true if the file will be lazy-created. 283 */ 284 public boolean isCreateOnDemand() { 285 return createOnDemand; 286 } 287 288 /** 289 * Returns the lock status. 290 * @return true if the file will be locked when writing, false otherwise. 291 */ 292 public boolean isLocking() { 293 return isLocking; 294 } 295 296 /** 297 * Returns the buffer size to use if the appender was configured with BufferedIO=true, otherwise returns a negative 298 * number. 299 * @return the buffer size, or a negative number if the output stream is not buffered 300 */ 301 public int getBufferSize() { 302 return bufferSize; 303 } 304 305 /** 306 * Returns posix file permissions if defined and the OS supports posix file attribute, 307 * null otherwise. 308 * @return File posix permissions 309 * @see PosixFileAttributeView 310 */ 311 public Set<PosixFilePermission> getFilePermissions() { 312 return filePermissions; 313 } 314 315 /** 316 * Returns file owner if defined and the OS supports owner file attribute view, 317 * null otherwise. 318 * @return File owner 319 * @see FileOwnerAttributeView 320 */ 321 public String getFileOwner() { 322 return fileOwner; 323 } 324 325 /** 326 * Returns file group if defined and the OS supports posix/group file attribute view, 327 * null otherwise. 328 * @return File group 329 * @see PosixFileAttributeView 330 */ 331 public String getFileGroup() { 332 return fileGroup; 333 } 334 335 /** 336 * Returns true if file attribute view enabled for this file manager. 337 * 338 * @return True if posix or owner supported and defined false otherwise. 339 */ 340 public boolean isAttributeViewEnabled() { 341 return attributeViewEnabled; 342 } 343 344 /** 345 * FileManager's content format is specified by: <code>Key: "fileURI" Value: provided "advertiseURI" param</code>. 346 * 347 * @return Map of content format keys supporting FileManager 348 */ 349 @Override 350 public Map<String, String> getContentFormat() { 351 final Map<String, String> result = new HashMap<>(super.getContentFormat()); 352 result.put("fileURI", advertiseURI); 353 return result; 354 } 355 356 /** 357 * Factory Data. 358 */ 359 private static class FactoryData extends ConfigurationFactoryData { 360 private final boolean append; 361 private final boolean locking; 362 private final boolean bufferedIo; 363 private final int bufferSize; 364 private final boolean createOnDemand; 365 private final String advertiseURI; 366 private final Layout<? extends Serializable> layout; 367 private final String filePermissions; 368 private final String fileOwner; 369 private final String fileGroup; 370 371 /** 372 * Constructor. 373 * @param append Append status. 374 * @param locking Locking status. 375 * @param bufferedIo Buffering flag. 376 * @param bufferSize Buffer size. 377 * @param createOnDemand if you want to lazy-create the file (a.k.a. on-demand.) 378 * @param advertiseURI the URI to use when advertising the file 379 * @param layout The layout 380 * @param filePermissions File permissions 381 * @param fileOwner File owner 382 * @param fileGroup File group 383 * @param configuration the configuration 384 */ 385 public FactoryData(final boolean append, final boolean locking, final boolean bufferedIo, final int bufferSize, 386 final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout, 387 final String filePermissions, final String fileOwner, final String fileGroup, 388 final Configuration configuration) { 389 super(configuration); 390 this.append = append; 391 this.locking = locking; 392 this.bufferedIo = bufferedIo; 393 this.bufferSize = bufferSize; 394 this.createOnDemand = createOnDemand; 395 this.advertiseURI = advertiseURI; 396 this.layout = layout; 397 this.filePermissions = filePermissions; 398 this.fileOwner = fileOwner; 399 this.fileGroup = fileGroup; 400 } 401 } 402 403 /** 404 * Factory to create a FileManager. 405 */ 406 private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> { 407 408 /** 409 * Creates a FileManager. 410 * @param name The name of the File. 411 * @param data The FactoryData 412 * @return The FileManager for the File. 413 */ 414 @Override 415 public FileManager createManager(final String name, final FactoryData data) { 416 final File file = new File(name); 417 try { 418 FileUtils.makeParentDirs(file); 419 final boolean writeHeader = !data.append || !file.exists(); 420 final int actualSize = data.bufferedIo ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE; 421 final ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[actualSize]); 422 final FileOutputStream fos = data.createOnDemand ? null : new FileOutputStream(file, data.append); 423 final FileManager fm = new FileManager(data.getLoggerContext(), name, fos, data.append, data.locking, 424 data.createOnDemand, data.advertiseURI, data.layout, 425 data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, byteBuffer); 426 if (fos != null && fm.attributeViewEnabled) { 427 fm.defineAttributeView(file.toPath()); 428 } 429 return fm; 430 } catch (final IOException ex) { 431 LOGGER.error("FileManager (" + name + ") " + ex, ex); 432 } 433 return null; 434 } 435 } 436 437}