| /* |
| * 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. |
| */ |
| 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; |
| |
| /** |
| * @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. |
| * @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); |
| createParentDir(file); |
| 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 time for {}", filename); |
| } |
| writeHeader(fos); |
| } |
| defineAttributeView(Paths.get(filename)); |
| return fos; |
| } |
| |
| protected void createParentDir(File file) { |
| } |
| |
| 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 {@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. |
| * @return The name of the File being managed. |
| */ |
| public String getFileName() { |
| return getName(); |
| } |
| /** |
| * 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. |
| * @return true if the file will be lazy-created. |
| */ |
| public boolean isCreateOnDemand() { |
| return createOnDemand; |
| } |
| |
| /** |
| * 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. |
| * @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. |
| * @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. |
| * @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. |
| * @return File group |
| * @see PosixFileAttributeView |
| */ |
| public String getFileGroup() { |
| return fileGroup; |
| } |
| |
| /** |
| * 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: <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. |
| */ |
| 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. |
| * @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. |
| */ |
| private static class FileManagerFactory implements ManagerFactory<FileManager, FactoryData> { |
| |
| /** |
| * 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 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 boolean writeHeader = file.exists() && file.length() == 0; |
| 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; |
| } |
| } |
| |
| } |