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