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.rolling; 018 019import java.io.File; 020import java.io.FileNotFoundException; 021import java.io.FileOutputStream; 022import java.io.IOException; 023import java.io.OutputStream; 024import java.io.Serializable; 025import java.nio.ByteBuffer; 026import java.util.concurrent.Semaphore; 027import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 028 029import org.apache.logging.log4j.core.Layout; 030import org.apache.logging.log4j.core.LogEvent; 031import org.apache.logging.log4j.core.appender.FileManager; 032import org.apache.logging.log4j.core.appender.ManagerFactory; 033import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction; 034import org.apache.logging.log4j.core.appender.rolling.action.Action; 035import org.apache.logging.log4j.core.util.Constants; 036import org.apache.logging.log4j.core.util.Log4jThread; 037 038/** 039 * The Rolling File Manager. 040 */ 041public class RollingFileManager extends FileManager { 042 043 private static RollingFileManagerFactory factory = new RollingFileManagerFactory(); 044 045 protected long size; 046 private long initialTime; 047 private final PatternProcessor patternProcessor; 048 private final Semaphore semaphore = new Semaphore(1); 049 private volatile TriggeringPolicy triggeringPolicy; 050 private volatile RolloverStrategy rolloverStrategy; 051 052 private static final AtomicReferenceFieldUpdater<RollingFileManager, TriggeringPolicy> triggeringPolicyUpdater = 053 AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, TriggeringPolicy.class, "triggeringPolicy"); 054 055 private static final AtomicReferenceFieldUpdater<RollingFileManager, RolloverStrategy> rolloverStrategyUpdater = 056 AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, RolloverStrategy.class, "rolloverStrategy"); 057 058 @Deprecated 059 protected RollingFileManager(final String fileName, final String pattern, final OutputStream os, 060 final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy, 061 final RolloverStrategy rolloverStrategy, final String advertiseURI, 062 final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) { 063 this(fileName, pattern, os, append, size, time, triggeringPolicy, rolloverStrategy, advertiseURI, layout, 064 writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE])); 065 } 066 067 protected RollingFileManager(final String fileName, final String pattern, final OutputStream os, 068 final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy, 069 final RolloverStrategy rolloverStrategy, final String advertiseURI, 070 final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) { 071 super(fileName, os, append, false, advertiseURI, layout, writeHeader, buffer); 072 this.size = size; 073 this.initialTime = time; 074 this.triggeringPolicy = triggeringPolicy; 075 this.rolloverStrategy = rolloverStrategy; 076 this.patternProcessor = new PatternProcessor(pattern); 077 triggeringPolicy.initialize(this); 078 } 079 080 /** 081 * Returns a RollingFileManager. 082 * @param fileName The file name. 083 * @param pattern The pattern for rolling file. 084 * @param append true if the file should be appended to. 085 * @param bufferedIO true if data should be buffered. 086 * @param policy The TriggeringPolicy. 087 * @param strategy The RolloverStrategy. 088 * @param advertiseURI the URI to use when advertising the file 089 * @param layout The Layout. 090 * @param bufferSize buffer size to use if bufferedIO is true 091 * @return A RollingFileManager. 092 */ 093 public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append, 094 final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy, 095 final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize, 096 final boolean immediateFlush) { 097 098 return (RollingFileManager) getManager(fileName, new FactoryData(pattern, append, 099 bufferedIO, policy, strategy, advertiseURI, layout, bufferSize, immediateFlush), factory); 100 } 101 102 // override to make visible for unit tests 103 @Override 104 protected synchronized void write(final byte[] bytes, final int offset, final int length, 105 final boolean immediateFlush) { 106 super.write(bytes, offset, length, immediateFlush); 107 } 108 109 @Override 110 protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) { 111 size += length; 112 super.writeToDestination(bytes, offset, length); 113 } 114 115 /** 116 * Returns the current size of the file. 117 * @return The size of the file in bytes. 118 */ 119 public long getFileSize() { 120 return size + byteBuffer.position(); 121 } 122 123 /** 124 * Returns the time the file was created. 125 * @return The time the file was created. 126 */ 127 public long getFileTime() { 128 return initialTime; 129 } 130 131 /** 132 * Determine if a rollover should occur. 133 * @param event The LogEvent. 134 */ 135 public synchronized void checkRollover(final LogEvent event) { 136 if (triggeringPolicy.isTriggeringEvent(event)) { 137 rollover(); 138 } 139 } 140 141 public synchronized void rollover() { 142 if (rollover(rolloverStrategy)) { 143 try { 144 size = 0; 145 initialTime = System.currentTimeMillis(); 146 createFileAfterRollover(); 147 } catch (final IOException e) { 148 logError("failed to create file after rollover", e); 149 } 150 } 151 } 152 153 protected void createFileAfterRollover() throws IOException { 154 final OutputStream os = new FileOutputStream(getFileName(), isAppend()); 155 setOutputStream(os); 156 } 157 158 /** 159 * Returns the pattern processor. 160 * @return The PatternProcessor. 161 */ 162 public PatternProcessor getPatternProcessor() { 163 return patternProcessor; 164 } 165 166 public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) { 167 triggeringPolicy.initialize(this); 168 triggeringPolicyUpdater.compareAndSet(this, this.triggeringPolicy, triggeringPolicy); 169 } 170 171 public void setRolloverStrategy(final RolloverStrategy rolloverStrategy) { 172 rolloverStrategyUpdater.compareAndSet(this, this.rolloverStrategy, rolloverStrategy); 173 } 174 175 /** 176 * Returns the triggering policy. 177 * @param <T> TriggeringPolicy type 178 * @return The TriggeringPolicy 179 */ 180 @SuppressWarnings("unchecked") 181 public <T extends TriggeringPolicy> T getTriggeringPolicy() { 182 // TODO We could parameterize this class with a TriggeringPolicy instead of type casting here. 183 return (T) this.triggeringPolicy; 184 } 185 186 /** 187 * Returns the rollover strategy. 188 * @return The RolloverStrategy 189 */ 190 public RolloverStrategy getRolloverStrategy() { 191 return this.rolloverStrategy; 192 } 193 194 private boolean rollover(final RolloverStrategy strategy) { 195 196 try { 197 // Block until the asynchronous operation is completed. 198 semaphore.acquire(); 199 } catch (final InterruptedException e) { 200 logError("Thread interrupted while attempting to check rollover", e); 201 return false; 202 } 203 204 boolean success = false; 205 Thread thread = null; 206 207 try { 208 final RolloverDescription descriptor = strategy.rollover(this); 209 if (descriptor != null) { 210 writeFooter(); 211 close(); 212 if (descriptor.getSynchronous() != null) { 213 LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous()); 214 try { 215 success = descriptor.getSynchronous().execute(); 216 } catch (final Exception ex) { 217 logError("caught error in synchronous task", ex); 218 } 219 } 220 221 if (success && descriptor.getAsynchronous() != null) { 222 LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous()); 223 thread = new Log4jThread(new AsyncAction(descriptor.getAsynchronous(), this)); 224 thread.start(); 225 } 226 return true; 227 } 228 return false; 229 } finally { 230 if (thread == null || !thread.isAlive()) { 231 semaphore.release(); 232 } 233 } 234 235 } 236 237 /** 238 * Performs actions asynchronously. 239 */ 240 private static class AsyncAction extends AbstractAction { 241 242 private final Action action; 243 private final RollingFileManager manager; 244 245 /** 246 * Constructor. 247 * @param act The action to perform. 248 * @param manager The manager. 249 */ 250 public AsyncAction(final Action act, final RollingFileManager manager) { 251 this.action = act; 252 this.manager = manager; 253 } 254 255 /** 256 * Perform an action. 257 * 258 * @return true if action was successful. A return value of false will cause 259 * the rollover to be aborted if possible. 260 * @throws java.io.IOException if IO error, a thrown exception will cause the rollover 261 * to be aborted if possible. 262 */ 263 @Override 264 public boolean execute() throws IOException { 265 try { 266 return action.execute(); 267 } finally { 268 manager.semaphore.release(); 269 } 270 } 271 272 /** 273 * Cancels the action if not already initialized or waits till completion. 274 */ 275 @Override 276 public void close() { 277 action.close(); 278 } 279 280 /** 281 * Determines if action has been completed. 282 * 283 * @return true if action is complete. 284 */ 285 @Override 286 public boolean isComplete() { 287 return action.isComplete(); 288 } 289 290 @Override 291 public String toString() { 292 StringBuilder builder = new StringBuilder(); 293 builder.append(super.toString()); 294 builder.append("[action="); 295 builder.append(action); 296 builder.append(", manager="); 297 builder.append(manager); 298 builder.append(", isComplete()="); 299 builder.append(isComplete()); 300 builder.append(", isInterrupted()="); 301 builder.append(isInterrupted()); 302 builder.append("]"); 303 return builder.toString(); 304 } 305 } 306 307 /** 308 * Factory data. 309 */ 310 private static class FactoryData { 311 private final String pattern; 312 private final boolean append; 313 private final boolean bufferedIO; 314 private final int bufferSize; 315 private final boolean immediateFlush; 316 private final TriggeringPolicy policy; 317 private final RolloverStrategy strategy; 318 private final String advertiseURI; 319 private final Layout<? extends Serializable> layout; 320 321 /** 322 * Create the data for the factory. 323 * @param pattern The pattern. 324 * @param append The append flag. 325 * @param bufferedIO The bufferedIO flag. 326 * @param advertiseURI 327 * @param layout The Layout. 328 * @param bufferSize the buffer size 329 * @param immediateFlush flush on every write or not 330 */ 331 public FactoryData(final String pattern, final boolean append, final boolean bufferedIO, 332 final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, 333 final Layout<? extends Serializable> layout, final int bufferSize, final boolean immediateFlush) { 334 this.pattern = pattern; 335 this.append = append; 336 this.bufferedIO = bufferedIO; 337 this.bufferSize = bufferSize; 338 this.policy = policy; 339 this.strategy = strategy; 340 this.advertiseURI = advertiseURI; 341 this.layout = layout; 342 this.immediateFlush = immediateFlush; 343 } 344 345 public TriggeringPolicy getTriggeringPolicy() 346 { 347 return this.policy; 348 } 349 350 public RolloverStrategy getRolloverStrategy() 351 { 352 return this.strategy; 353 } 354 355 @Override 356 public String toString() { 357 StringBuilder builder = new StringBuilder(); 358 builder.append(super.toString()); 359 builder.append("[pattern="); 360 builder.append(pattern); 361 builder.append(", append="); 362 builder.append(append); 363 builder.append(", bufferedIO="); 364 builder.append(bufferedIO); 365 builder.append(", bufferSize="); 366 builder.append(bufferSize); 367 builder.append(", policy="); 368 builder.append(policy); 369 builder.append(", strategy="); 370 builder.append(strategy); 371 builder.append(", advertiseURI="); 372 builder.append(advertiseURI); 373 builder.append(", layout="); 374 builder.append(layout); 375 builder.append("]"); 376 return builder.toString(); 377 } 378 } 379 380 @Override 381 public void updateData(final Object data) 382 { 383 final FactoryData factoryData = (FactoryData) data; 384 setRolloverStrategy(factoryData.getRolloverStrategy()); 385 setTriggeringPolicy(factoryData.getTriggeringPolicy()); 386 } 387 388 /** 389 * Factory to create a RollingFileManager. 390 */ 391 private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> { 392 393 /** 394 * Creates a RollingFileManager. 395 * @param name The name of the entity to manage. 396 * @param data The data required to create the entity. 397 * @return a RollingFileManager. 398 */ 399 @Override 400 public RollingFileManager createManager(final String name, final FactoryData data) { 401 final File file = new File(name); 402 final File parent = file.getParentFile(); 403 if (null != parent && !parent.exists()) { 404 parent.mkdirs(); 405 } 406 // LOG4J2-1140: check writeHeader before creating the file 407 final boolean writeHeader = !data.append || !file.exists(); 408 try { 409 file.createNewFile(); 410 } catch (final IOException ioe) { 411 LOGGER.error("Unable to create file " + name, ioe); 412 return null; 413 } 414 final long size = data.append ? file.length() : 0; 415 416 OutputStream os; 417 try { 418 os = new FileOutputStream(name, data.append); 419 final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE; 420 final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]); 421 422 final long time = file.lastModified(); // LOG4J2-531 create file first so time has valid value 423 return new RollingFileManager(name, data.pattern, os, data.append, size, time, data.policy, 424 data.strategy, data.advertiseURI, data.layout, writeHeader, buffer); 425 } catch (final FileNotFoundException ex) { 426 LOGGER.error("FileManager (" + name + ") " + ex, ex); 427 } 428 return null; 429 } 430 } 431 432}