package org.apache.logging.log4j.core.appender.rolling;
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.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LifeCycle;
import org.apache.logging.log4j.core.LifeCycle2;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConfigurationFactoryData;
import org.apache.logging.log4j.core.appender.FileManager;
import org.apache.logging.log4j.core.appender.ManagerFactory;
import org.apache.logging.log4j.core.appender.rolling.action.AbstractAction;
import org.apache.logging.log4j.core.appender.rolling.action.Action;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.FileUtils;
import org.apache.logging.log4j.core.util.Log4jThreadFactory;
public class RollingFileManager extends FileManager {
private static RollingFileManagerFactory factory = new RollingFileManagerFactory();
private static final int MAX_TRIES = 3;
private static final int MIN_DURATION = 100;
private static final FileTime EPOCH = FileTime.fromMillis(0);
protected long size;
private long initialTime;
private volatile PatternProcessor patternProcessor;
private final Semaphore semaphore = new Semaphore(1);
private final Log4jThreadFactory threadFactory = Log4jThreadFactory.createThreadFactory("RollingFileManager");
private volatile TriggeringPolicy triggeringPolicy;
private volatile RolloverStrategy rolloverStrategy;
private volatile boolean renameEmptyFiles = false;
private volatile boolean initialized = false;
private volatile String fileName;
private final FileExtension fileExtension;
private final ExecutorService asyncExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0, TimeUnit.MILLISECONDS,
new EmptyQueue(), threadFactory);
private static final AtomicReferenceFieldUpdater<RollingFileManager, TriggeringPolicy> triggeringPolicyUpdater =
AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, TriggeringPolicy.class, "triggeringPolicy");
private static final AtomicReferenceFieldUpdater<RollingFileManager, RolloverStrategy> rolloverStrategyUpdater =
AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, RolloverStrategy.class, "rolloverStrategy");
private static final AtomicReferenceFieldUpdater<RollingFileManager, PatternProcessor> patternProcessorUpdater =
AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, PatternProcessor.class, "patternProcessor");
@Deprecated
protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
final boolean append, final long size, final long initialTime, final TriggeringPolicy triggeringPolicy,
final RolloverStrategy rolloverStrategy, final String advertiseURI,
final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) {
this(fileName, pattern, os, append, size, initialTime, triggeringPolicy, rolloverStrategy, advertiseURI, layout,
writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE]));
}
@Deprecated
protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
final boolean append, final long size, final long initialTime, final TriggeringPolicy triggeringPolicy,
final RolloverStrategy rolloverStrategy, final String advertiseURI,
final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
super(fileName != null ? fileName : pattern, os, append, false, advertiseURI, layout, writeHeader,
buffer);
this.size = size;
this.initialTime = initialTime;
this.triggeringPolicy = triggeringPolicy;
this.rolloverStrategy = rolloverStrategy;
this.patternProcessor = new PatternProcessor(pattern);
this.patternProcessor.setPrevFileTime(initialTime);
this.fileName = fileName;
this.fileExtension = FileExtension.lookupForFile(pattern);
}
@Deprecated
protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
final boolean append, final boolean createOnDemand, final long size, final long initialTime,
final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
super(loggerContext, fileName != null ? fileName : pattern, os, append, false, createOnDemand,
advertiseURI, layout, writeHeader, buffer);
this.size = size;
this.initialTime = initialTime;
this.triggeringPolicy = triggeringPolicy;
this.rolloverStrategy = rolloverStrategy;
this.patternProcessor = new PatternProcessor(pattern);
this.patternProcessor.setPrevFileTime(initialTime);
this.fileName = fileName;
this.fileExtension = FileExtension.lookupForFile(pattern);
}
protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
final boolean append, final boolean createOnDemand, final long size, final long initialTime,
final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
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, fileName != null ? fileName : pattern, os, append, false, createOnDemand,
advertiseURI, layout, filePermissions, fileOwner, fileGroup, writeHeader, buffer);
this.size = size;
this.initialTime = initialTime;
this.triggeringPolicy = triggeringPolicy;
this.rolloverStrategy = rolloverStrategy;
this.patternProcessor = new PatternProcessor(pattern);
this.patternProcessor.setPrevFileTime(initialTime);
this.fileName = fileName;
this.fileExtension = FileExtension.lookupForFile(pattern);
}
public void initialize() {
if (!initialized) {
LOGGER.debug("Initializing triggering policy {}", triggeringPolicy);
initialized = true;
triggeringPolicy.initialize(this);
if (triggeringPolicy instanceof LifeCycle) {
((LifeCycle) triggeringPolicy).start();
}
if (rolloverStrategy instanceof DirectFileRolloverStrategy) {
File file = new File(getFileName());
if (file.exists()) {
size = file.length();
} else {
((DirectFileRolloverStrategy) rolloverStrategy).clearCurrentFileName();
}
}
}
}
public static RollingFileManager getFileManager(final String fileName, final String pattern, final boolean append,
final boolean bufferedIO, final TriggeringPolicy policy, final RolloverStrategy strategy,
final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize,
final boolean immediateFlush, final boolean createOnDemand,
final String filePermissions, final String fileOwner, final String fileGroup,
final Configuration configuration) {
if (strategy instanceof DirectWriteRolloverStrategy && fileName != null) {
LOGGER.error("The fileName attribute must not be specified with the DirectWriteRolloverStrategy");
return null;
}
final String name = fileName == null ? pattern : fileName;
return narrow(RollingFileManager.class, getManager(name, new FactoryData(fileName, pattern, append,
bufferedIO, policy, strategy, advertiseURI, layout, bufferSize, immediateFlush, createOnDemand,
filePermissions, fileOwner, fileGroup, configuration), factory));
}
@Override
public String getFileName() {
if (rolloverStrategy instanceof DirectFileRolloverStrategy) {
fileName = ((DirectFileRolloverStrategy) rolloverStrategy).getCurrentFileName(this);
}
return fileName;
}
public FileExtension getFileExtension() {
return fileExtension;
}
@Override
protected synchronized void write(final byte[] bytes, final int offset, final int length,
final boolean immediateFlush) {
super.write(bytes, offset, length, immediateFlush);
}
@Override
protected synchronized void writeToDestination(final byte[] bytes, final int offset, final int length) {
size += length;
super.writeToDestination(bytes, offset, length);
}
public boolean isRenameEmptyFiles() {
return renameEmptyFiles;
}
public void setRenameEmptyFiles(final boolean renameEmptyFiles) {
this.renameEmptyFiles = renameEmptyFiles;
}
public long getFileSize() {
return size + byteBuffer.position();
}
public long getFileTime() {
return initialTime;
}
public synchronized void checkRollover(final LogEvent event) {
if (triggeringPolicy.isTriggeringEvent(event)) {
rollover();
}
}
@Override
public boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
LOGGER.debug("Shutting down RollingFileManager {}", getName());
boolean stopped = true;
if (triggeringPolicy instanceof LifeCycle2) {
stopped &= ((LifeCycle2) triggeringPolicy).stop(timeout, timeUnit);
} else if (triggeringPolicy instanceof LifeCycle) {
((LifeCycle) triggeringPolicy).stop();
stopped &= true;
}
final boolean status = super.releaseSub(timeout, timeUnit) && stopped;
asyncExecutor.shutdown();
try {
final long millis = timeUnit.toMillis(timeout);
final long waitInterval = MIN_DURATION < millis ? millis : MIN_DURATION;
for (int count = 1; count <= MAX_TRIES && !asyncExecutor.isTerminated(); ++count) {
asyncExecutor.awaitTermination(waitInterval * count, TimeUnit.MILLISECONDS);
}
if (asyncExecutor.isTerminated()) {
LOGGER.debug("All asynchronous threads have terminated");
} else {
asyncExecutor.shutdownNow();
try {
asyncExecutor.awaitTermination(timeout, timeUnit);
if (asyncExecutor.isTerminated()) {
LOGGER.debug("All asynchronous threads have terminated");
} else {
LOGGER.debug("RollingFileManager shutting down but some asynchronous services may not have completed");
}
} catch (final InterruptedException inner) {
LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
}
}
} catch (final InterruptedException ie) {
asyncExecutor.shutdownNow();
try {
asyncExecutor.awaitTermination(timeout, timeUnit);
if (asyncExecutor.isTerminated()) {
LOGGER.debug("All asynchronous threads have terminated");
}
} catch (final InterruptedException inner) {
LOGGER.warn("RollingFileManager stopped but some asynchronous services may not have completed.");
}
Thread.currentThread().interrupt();
}
LOGGER.debug("RollingFileManager shutdown completed with status {}", status);
return status;
}
public synchronized void rollover(Date prevFileTime, Date prevRollTime) {
getPatternProcessor().setPrevFileTime(prevFileTime.getTime());
getPatternProcessor().setCurrentFileTime(prevRollTime.getTime());
rollover();
}
public synchronized void rollover() {
if (!hasOutputStream()) {
return;
}
if (rollover(rolloverStrategy)) {
try {
size = 0;
initialTime = System.currentTimeMillis();
createFileAfterRollover();
} catch (final IOException e) {
logError("Failed to create file after rollover", e);
}
}
}
protected void createFileAfterRollover() throws IOException {
setOutputStream(createOutputStream());
}
public PatternProcessor getPatternProcessor() {
return patternProcessor;
}
public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) {
triggeringPolicy.initialize(this);
final TriggeringPolicy policy = this.triggeringPolicy;
int count = 0;
boolean policyUpdated = false;
do {
++count;
} while (!(policyUpdated = triggeringPolicyUpdater.compareAndSet(this, this.triggeringPolicy, triggeringPolicy))
&& count < MAX_TRIES);
if (policyUpdated) {
if (triggeringPolicy instanceof LifeCycle) {
((LifeCycle) triggeringPolicy).start();
}
if (policy instanceof LifeCycle) {
((LifeCycle) policy).stop();
}
} else {
if (triggeringPolicy instanceof LifeCycle) {
((LifeCycle) triggeringPolicy).stop();
}
}
}
public void setRolloverStrategy(final RolloverStrategy rolloverStrategy) {
rolloverStrategyUpdater.compareAndSet(this, this.rolloverStrategy, rolloverStrategy);
}
public void setPatternProcessor(final PatternProcessor patternProcessor) {
patternProcessorUpdater.compareAndSet(this, this.patternProcessor, patternProcessor);
}
@SuppressWarnings("unchecked")
public <T extends TriggeringPolicy> T getTriggeringPolicy() {
return (T) this.triggeringPolicy;
}
public RolloverStrategy getRolloverStrategy() {
return this.rolloverStrategy;
}
private boolean rollover(final RolloverStrategy strategy) {
boolean releaseRequired = false;
try {
semaphore.acquire();
releaseRequired = true;
} catch (final InterruptedException e) {
logError("Thread interrupted while attempting to check rollover", e);
return false;
}
boolean success = true;
try {
final RolloverDescription descriptor = strategy.rollover(this);
if (descriptor != null) {
writeFooter();
closeOutputStream();
if (descriptor.getSynchronous() != null) {
LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous());
try {
success = descriptor.getSynchronous().execute();
} catch (final Exception ex) {
success = false;
logError("Caught error in synchronous task", ex);
}
}
if (success && descriptor.getAsynchronous() != null) {
LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous());
asyncExecutor.execute(new AsyncAction(descriptor.getAsynchronous(), this));
releaseRequired = false;
}
return true;
}
return false;
} finally {
if (releaseRequired) {
semaphore.release();
}
}
}
private static class AsyncAction extends AbstractAction {
private final Action action;
private final RollingFileManager manager;
public AsyncAction(final Action act, final RollingFileManager manager) {
this.action = act;
this.manager = manager;
}
@Override
public boolean execute() throws IOException {
try {
return action.execute();
} finally {
manager.semaphore.release();
}
}
@Override
public void close() {
action.close();
}
@Override
public boolean isComplete() {
return action.isComplete();
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(super.toString());
builder.append("[action=");
builder.append(action);
builder.append(", manager=");
builder.append(manager);
builder.append(", isComplete()=");
builder.append(isComplete());
builder.append(", isInterrupted()=");
builder.append(isInterrupted());
builder.append("]");
return builder.toString();
}
}
private static class FactoryData extends ConfigurationFactoryData {
private final String fileName;
private final String pattern;
private final boolean append;
private final boolean bufferedIO;
private final int bufferSize;
private final boolean immediateFlush;
private final boolean createOnDemand;
private final TriggeringPolicy policy;
private final RolloverStrategy strategy;
private final String advertiseURI;
private final Layout<? extends Serializable> layout;
private final String filePermissions;
private final String fileOwner;
private final String fileGroup;
public FactoryData(final String fileName, final String pattern, final boolean append, final boolean bufferedIO,
final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
final Layout<? extends Serializable> layout, final int bufferSize, final boolean immediateFlush,
final boolean createOnDemand, final String filePermissions, final String fileOwner, final String fileGroup,
final Configuration configuration) {
super(configuration);
this.fileName = fileName;
this.pattern = pattern;
this.append = append;
this.bufferedIO = bufferedIO;
this.bufferSize = bufferSize;
this.policy = policy;
this.strategy = strategy;
this.advertiseURI = advertiseURI;
this.layout = layout;
this.immediateFlush = immediateFlush;
this.createOnDemand = createOnDemand;
this.filePermissions = filePermissions;
this.fileOwner = fileOwner;
this.fileGroup = fileGroup;
}
public TriggeringPolicy getTriggeringPolicy() {
return this.policy;
}
public RolloverStrategy getRolloverStrategy() {
return this.strategy;
}
public String getPattern() {
return pattern;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(super.toString());
builder.append("[pattern=");
builder.append(pattern);
builder.append(", append=");
builder.append(append);
builder.append(", bufferedIO=");
builder.append(bufferedIO);
builder.append(", bufferSize=");
builder.append(bufferSize);
builder.append(", policy=");
builder.append(policy);
builder.append(", strategy=");
builder.append(strategy);
builder.append(", advertiseURI=");
builder.append(advertiseURI);
builder.append(", layout=");
builder.append(layout);
builder.append(", filePermissions=");
builder.append(filePermissions);
builder.append(", fileOwner=");
builder.append(fileOwner);
builder.append("]");
return builder.toString();
}
}
@Override
public void updateData(final Object data) {
final FactoryData factoryData = (FactoryData) data;
setRolloverStrategy(factoryData.getRolloverStrategy());
setTriggeringPolicy(factoryData.getTriggeringPolicy());
setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor()));
}
private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> {
@Override
public RollingFileManager createManager(final String name, final FactoryData data) {
long size = 0;
boolean writeHeader = !data.append;
File file = null;
if (data.fileName != null) {
file = new File(data.fileName);
writeHeader = !data.append || !file.exists();
try {
FileUtils.makeParentDirs(file);
final boolean created = data.createOnDemand ? false : file.createNewFile();
LOGGER.trace("New file '{}' created = {}", name, created);
} catch (final IOException ioe) {
LOGGER.error("Unable to create file " + name, ioe);
return null;
}
size = data.append ? file.length() : 0;
}
try {
final int actualSize = data.bufferedIO ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]);
final OutputStream os = data.createOnDemand || data.fileName == null ? null :
new FileOutputStream(data.fileName, data.append);
final long initialTime = data.createOnDemand || file == null ?
0 : initialFileTime(file);
final RollingFileManager rm = new RollingFileManager(data.getLoggerContext(), data.fileName, data.pattern, os,
data.append, data.createOnDemand, size, initialTime, data.policy, data.strategy, data.advertiseURI,
data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, buffer);
if (os != null && rm.isAttributeViewEnabled()) {
rm.defineAttributeView(file.toPath());
}
return rm;
} catch (final IOException ex) {
LOGGER.error("RollingFileManager (" + name + ") " + ex, ex);
}
return null;
}
}
private static long initialFileTime(final File file) {
final Path path = file.toPath();
if (Files.exists(path)) {
try {
final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
final FileTime fileTime = attrs.creationTime();
if (fileTime.compareTo(EPOCH) > 0) {
LOGGER.debug("Returning file creation time for {}", file.getAbsolutePath());
return fileTime.toMillis();
}
LOGGER.info("Unable to obtain file creation time for " + file.getAbsolutePath());
} catch (final Exception ex) {
LOGGER.info("Unable to calculate file creation time for " + file.getAbsolutePath() + ": " + ex.getMessage());
}
}
return file.lastModified();
}
private static class EmptyQueue extends ArrayBlockingQueue<Runnable> {
private static final long serialVersionUID = 1L;
EmptyQueue() {
super(1);
}
@Override
public int remainingCapacity() {
return 0;
}
@Override
public boolean add(final Runnable runnable) {
throw new IllegalStateException("Queue is full");
}
@Override
public void put(final Runnable runnable) throws InterruptedException {
throw new InterruptedException("Unable to insert into queue");
}
@Override
public boolean offer(final Runnable runnable, final long timeout, final TimeUnit timeUnit) throws InterruptedException {
Thread.sleep(timeUnit.toMillis(timeout));
return false;
}
@Override
public boolean addAll(final Collection<? extends Runnable> collection) {
if (collection.size() > 0) {
throw new IllegalArgumentException("Too many items in collection");
}
return false;
}
}
}