/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache license, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the license for the specific language governing permissions and
 * limitations under the license.
 */
package org.apache.logging.log4j.core.appender;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.FileUtils;


Manages actual File I/O for File Appenders.
/** * Manages actual File I/O for File Appenders. */
public class FileManager extends OutputStreamManager { private static final FileManagerFactory FACTORY = new FileManagerFactory(); private final boolean isAppend; private final boolean createOnDemand; private final boolean isLocking; private final String advertiseURI; private final int bufferSize; private final Set<PosixFilePermission> filePermissions; private final String fileOwner; private final String fileGroup; private final boolean attributeViewEnabled;
Deprecated:
/** * @deprecated */
@Deprecated protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking, final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) { this(fileName, os, append, locking, advertiseURI, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize])); }
Deprecated:
Since:2.6
/** * @deprecated * @since 2.6 */
@Deprecated protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking, final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) { super(os, fileName, layout, writeHeader, buffer); this.isAppend = append; this.createOnDemand = false; this.isLocking = locking; this.advertiseURI = advertiseURI; this.bufferSize = buffer.capacity(); this.filePermissions = null; this.fileOwner = null; this.fileGroup = null; this.attributeViewEnabled = false; }
Deprecated:
Since:2.7
/** * @deprecated * @since 2.7 */
@Deprecated protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking, final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) { super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer); this.isAppend = append; this.createOnDemand = createOnDemand; this.isLocking = locking; this.advertiseURI = advertiseURI; this.bufferSize = buffer.capacity(); this.filePermissions = null; this.fileOwner = null; this.fileGroup = null; this.attributeViewEnabled = false; }
Since:2.9
/** * @since 2.9 */
protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking, final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout, final String filePermissions, final String fileOwner, final String fileGroup, final boolean writeHeader, final ByteBuffer buffer) { super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer); this.isAppend = append; this.createOnDemand = createOnDemand; this.isLocking = locking; this.advertiseURI = advertiseURI; this.bufferSize = buffer.capacity(); final Set<String> views = FileSystems.getDefault().supportedFileAttributeViews(); if (views.contains("posix")) { this.filePermissions = filePermissions != null ? PosixFilePermissions.fromString(filePermissions) : null; this.fileGroup = fileGroup; } else { this.filePermissions = null; this.fileGroup = null; if (filePermissions != null) { LOGGER.warn("Posix file attribute permissions defined but it is not supported by this files system."); } if (fileGroup != null) { LOGGER.warn("Posix file attribute group defined but it is not supported by this files system."); } } if (views.contains("owner")) { this.fileOwner = fileOwner; } else { this.fileOwner = null; if (fileOwner != null) { LOGGER.warn("Owner file attribute defined but it is not supported by this files system."); } } // Supported and defined this.attributeViewEnabled = this.filePermissions != null || this.fileOwner != null || this.fileGroup != null; }
Returns the FileManager.
Params:
  • fileName – The name of the file to manage.
  • append – true if the file should be appended to, false if it should be overwritten.
  • locking – true if the file should be locked while writing, false otherwise.
  • bufferedIo – true if the contents should be buffered as they are written.
  • createOnDemand – true if you want to lazy-create the file (a.k.a. on-demand.)
  • advertiseUri – the URI to use when advertising the file
  • layout – The layout
  • bufferSize – buffer size for buffered IO
  • filePermissions – File permissions
  • fileOwner – File owner
  • fileGroup – File group
  • configuration – The configuration.
Returns:A FileManager for the File.
/** * Returns the FileManager. * @param fileName The name of the file to manage. * @param append true if the file should be appended to, false if it should be overwritten. * @param locking true if the file should be locked while writing, false otherwise. * @param bufferedIo true if the contents should be buffered as they are written. * @param createOnDemand true if you want to lazy-create the file (a.k.a. on-demand.) * @param advertiseUri the URI to use when advertising the file * @param layout The layout * @param bufferSize buffer size for buffered IO * @param filePermissions File permissions * @param fileOwner File owner * @param fileGroup File group * @param configuration The configuration. * @return A FileManager for the File. */
public static FileManager getFileManager(final String fileName, final boolean append, boolean locking, final boolean bufferedIo, final boolean createOnDemand, final String advertiseUri, final Layout<? extends Serializable> layout, final int bufferSize, final String filePermissions, final String fileOwner, final String fileGroup, final Configuration configuration) { if (locking && bufferedIo) { locking = false; } return narrow(FileManager.class, getManager(fileName, new FactoryData(append, locking, bufferedIo, bufferSize, createOnDemand, advertiseUri, layout, filePermissions, fileOwner, fileGroup, configuration), FACTORY)); } @Override protected OutputStream createOutputStream() throws IOException { final String filename = getFileName(); LOGGER.debug("Now writing to {} at {}", filename, new Date()); final File file = new File(filename); final FileOutputStream fos = new FileOutputStream(file, isAppend); if (file.exists() && file.length() == 0) { try { FileTime now = FileTime.fromMillis(System.currentTimeMillis()); Files.setAttribute(file.toPath(), "creationTime", now); } catch (Exception ex) { LOGGER.warn("Unable to set current file tiem for {}", filename); } } defineAttributeView(Paths.get(filename)); return fos; } protected void defineAttributeView(final Path path) { if (attributeViewEnabled) { try { // FileOutputStream may not create new file on all jvm path.toFile().createNewFile(); FileUtils.defineFilePosixAttributeView(path, filePermissions, fileOwner, fileGroup); } catch (final Exception e) { LOGGER.error("Could not define attribute view on path \"{}\" got {}", path, e.getMessage(), e); } } } @Override protected synchronized void write(final byte[] bytes, final int offset, final int length, final boolean immediateFlush) { if (isLocking) { try { @SuppressWarnings("resource") final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel(); /* * Lock the whole file. This could be optimized to only lock from the current file position. Note that * locking may be advisory on some systems and mandatory on others, so locking just from the current * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an * exception if the region of the file is already locked by another FileChannel in the same JVM. * Hopefully, that will be avoided since every file should have a single file manager - unless two * different files strings are configured that somehow map to the same file. */ try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) { super.write(bytes, offset, length, immediateFlush); } } catch (final IOException ex) { throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex); } } else { super.write(bytes, offset, length, immediateFlush); } }
Overrides OutputStreamManager.writeToDestination(byte[], int, int) to add support for file locking.
Params:
  • bytes – the array containing data
  • offset – from where to write
  • length – how many bytes to write
Since:2.8
/** * Overrides {@link OutputStreamManager#writeToDestination(byte[], int, int)} to add support for file locking. * * @param bytes the array containing data * @param offset from where to write * @param length how many bytes to write * @since 2.8 */
@Override protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { if (isLocking) { try { @SuppressWarnings("resource") final FileChannel channel = ((FileOutputStream) getOutputStream()).getChannel(); /* * Lock the whole file. This could be optimized to only lock from the current file position. Note that * locking may be advisory on some systems and mandatory on others, so locking just from the current * position would allow reading on systems where locking is mandatory. Also, Java 6 will throw an * exception if the region of the file is already locked by another FileChannel in the same JVM. * Hopefully, that will be avoided since every file should have a single file manager - unless two * different files strings are configured that somehow map to the same file. */ try (final FileLock lock = channel.lock(0, Long.MAX_VALUE, false)) { super.writeToDestination(bytes, offset, length); } } catch (final IOException ex) { throw new AppenderLoggingException("Unable to obtain lock on " + getName(), ex); } } else { super.writeToDestination(bytes, offset, length); } }
Returns the name of the File being managed.
Returns:The name of the File being managed.
/** * Returns the name of the File being managed. * @return The name of the File being managed. */
public String getFileName() { return getName(); }
Returns the append status.
Returns:true if the file will be appended to, false if it is overwritten.
/** * Returns the append status. * @return true if the file will be appended to, false if it is overwritten. */
public boolean isAppend() { return isAppend; }
Returns the lazy-create.
Returns:true if the file will be lazy-created.
/** * Returns the lazy-create. * @return true if the file will be lazy-created. */
public boolean isCreateOnDemand() { return createOnDemand; }
Returns the lock status.
Returns:true if the file will be locked when writing, false otherwise.
/** * Returns the lock status. * @return true if the file will be locked when writing, false otherwise. */
public boolean isLocking() { return isLocking; }
Returns the buffer size to use if the appender was configured with BufferedIO=true, otherwise returns a negative number.
Returns:the buffer size, or a negative number if the output stream is not buffered
/** * Returns the buffer size to use if the appender was configured with BufferedIO=true, otherwise returns a negative * number. * @return the buffer size, or a negative number if the output stream is not buffered */
public int getBufferSize() { return bufferSize; }
Returns posix file permissions if defined and the OS supports posix file attribute, null otherwise.
See Also:
Returns:File posix permissions
/** * Returns posix file permissions if defined and the OS supports posix file attribute, * null otherwise. * @return File posix permissions * @see PosixFileAttributeView */
public Set<PosixFilePermission> getFilePermissions() { return filePermissions; }
Returns file owner if defined and the OS supports owner file attribute view, null otherwise.
See Also:
Returns:File owner
/** * Returns file owner if defined and the OS supports owner file attribute view, * null otherwise. * @return File owner * @see FileOwnerAttributeView */
public String getFileOwner() { return fileOwner; }
Returns file group if defined and the OS supports posix/group file attribute view, null otherwise.
See Also:
Returns:File group
/** * Returns file group if defined and the OS supports posix/group file attribute view, * null otherwise. * @return File group * @see PosixFileAttributeView */
public String getFileGroup() { return fileGroup; }
Returns true if file attribute view enabled for this file manager.
Returns:True if posix or owner supported and defined false otherwise.
/** * Returns true if file attribute view enabled for this file manager. * * @return True if posix or owner supported and defined false otherwise. */
public boolean isAttributeViewEnabled() { return attributeViewEnabled; }
FileManager's content format is specified by: Key: "fileURI" Value: provided "advertiseURI" param.
Returns:Map of content format keys supporting FileManager
/** * FileManager's content format is specified by: <code>Key: "fileURI" Value: provided "advertiseURI" param</code>. * * @return Map of content format keys supporting FileManager */
@Override public Map<String, String> getContentFormat() { final Map<String, String> result = new HashMap<>(super.getContentFormat()); result.put("fileURI", advertiseURI); return result; }
Factory Data.
/** * Factory Data. */
private static class FactoryData extends ConfigurationFactoryData { private final boolean append; private final boolean locking; private final boolean bufferedIo; private final int bufferSize; private final boolean createOnDemand; private final String advertiseURI; private final Layout<? extends Serializable> layout; private final String filePermissions; private final String fileOwner; private final String fileGroup;
Constructor.
Params:
  • append – Append status.
  • locking – Locking status.
  • bufferedIo – Buffering flag.
  • bufferSize – Buffer size.
  • createOnDemand – if you want to lazy-create the file (a.k.a. on-demand.)
  • advertiseURI – the URI to use when advertising the file
  • layout – The layout
  • filePermissions – File permissions
  • fileOwner – File owner
  • fileGroup – File group
  • configuration – the configuration
/** * Constructor. * @param append Append status. * @param locking Locking status. * @param bufferedIo Buffering flag. * @param bufferSize Buffer size. * @param createOnDemand if you want to lazy-create the file (a.k.a. on-demand.) * @param advertiseURI the URI to use when advertising the file * @param layout The layout * @param filePermissions File permissions * @param fileOwner File owner * @param fileGroup File group * @param configuration the configuration */
public FactoryData(final boolean append, final boolean locking, final boolean bufferedIo, final int bufferSize, final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout, final String filePermissions, final String fileOwner, final String fileGroup, final Configuration configuration) { super(configuration); this.append = append; this.locking = locking; this.bufferedIo = bufferedIo; this.bufferSize = bufferSize; this.createOnDemand = createOnDemand; this.advertiseURI = advertiseURI; this.layout = layout; this.filePermissions = filePermissions; this.fileOwner = fileOwner; this.fileGroup = fileGroup; } }
Factory to create a FileManager.
/** * Factory to create a FileManager. */
private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> {
Creates a FileManager.
Params:
  • name – The name of the File.
  • data – The FactoryData
Returns:The FileManager for the File.
/** * Creates a FileManager. * @param name The name of the File. * @param data The FactoryData * @return The FileManager for the File. */
@Override public FileManager createManager(final String name, final FactoryData data) { final File file = new File(name); try { FileUtils.makeParentDirs(file); final boolean writeHeader = !data.append || !file.exists(); final int actualSize = data.bufferedIo ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE; final ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[actualSize]); final FileOutputStream fos = data.createOnDemand ? null : new FileOutputStream(file, data.append); final FileManager fm = new FileManager(data.getLoggerContext(), name, fos, data.append, data.locking, data.createOnDemand, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, byteBuffer); if (fos != null && fm.attributeViewEnabled) { fm.defineAttributeView(file.toPath()); } return fm; } catch (final IOException ex) { LOGGER.error("FileManager (" + name + ") " + ex, ex); } return null; } } }