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