LOG4J2-381 - Allow triggering policy and rollover strategy to be modified during reconfiguration
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/3c84ef94 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/3c84ef94 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/3c84ef94 Branch: refs/heads/master Commit: 3c84ef9450cc57274d2f59767002fb0bb99b815a Parents: a7977f5 Author: Ralph Goers <[email protected]> Authored: Wed Nov 18 17:42:45 2015 -0700 Committer: Ralph Goers <[email protected]> Committed: Wed Nov 18 17:42:45 2015 -0700 ---------------------------------------------------------------------- .../log4j/core/appender/AbstractManager.java | 5 + .../core/appender/AbstractManager.java.orig | 166 +++++++++ .../appender/rolling/RollingFileManager.java | 40 ++- .../rolling/RollingFileManager.java.orig | 343 +++++++++++++++++++ .../rolling/RollingRandomAccessFileManager.java | 17 + .../RollingRandomAccessFileManager.java.orig | 252 ++++++++++++++ src/changes/changes.xml | 6 + 7 files changed, 827 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3c84ef94/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java index d256a4f..45f5141 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java @@ -76,6 +76,8 @@ public abstract class AbstractManager { + name + "] with data [" + data + "]"); } MAP.put(name, manager); + } else { + manager.updateData(data); } manager.count++; return manager; @@ -84,6 +86,9 @@ public abstract class AbstractManager { } } + public void updateData(final Object data) { + } + /** * Determines if a Manager with the specified name exists. * @param name The name of the Manager. http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3c84ef94/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java.orig ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java.orig b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java.orig new file mode 100644 index 0000000..d256a4f --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractManager.java.orig @@ -0,0 +1,166 @@ +/* + * 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.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Abstract base class used to register managers. + */ +public abstract class AbstractManager { + + /** + * Allow subclasses access to the status logger without creating another instance. + */ + protected static final Logger LOGGER = StatusLogger.getLogger(); + + // Need to lock that map instead of using a ConcurrentMap due to stop removing the + // manager from the map and closing the stream, requiring the whole stop method to be locked. + private static final Map<String, AbstractManager> MAP = new HashMap<>(); + + private static final Lock LOCK = new ReentrantLock(); + + /** + * Number of Appenders using this manager. + */ + protected int count; + + private final String name; + + protected AbstractManager(final String name) { + this.name = name; + LOGGER.debug("Starting {} {}", this.getClass().getSimpleName(), name); + } + + /** + * Retrieves a Manager if it has been previously created or creates a new Manager. + * @param name The name of the Manager to retrieve. + * @param factory The Factory to use to create the Manager. + * @param data An Object that should be passed to the factory when creating the Manager. + * @param <M> The Type of the Manager to be created. + * @param <T> The type of the Factory data. + * @return A Manager with the specified name and type. + */ + public static <M extends AbstractManager, T> M getManager(final String name, final ManagerFactory<M, T> factory, + final T data) { + LOCK.lock(); + try { + @SuppressWarnings("unchecked") + M manager = (M) MAP.get(name); + if (manager == null) { + manager = factory.createManager(name, data); + if (manager == null) { + throw new IllegalStateException("ManagerFactory [" + factory + "] unable to create manager for [" + + name + "] with data [" + data + "]"); + } + MAP.put(name, manager); + } + manager.count++; + return manager; + } finally { + LOCK.unlock(); + } + } + + /** + * Determines if a Manager with the specified name exists. + * @param name The name of the Manager. + * @return True if the Manager exists, false otherwise. + */ + public static boolean hasManager(final String name) { + LOCK.lock(); + try { + return MAP.containsKey(name); + } finally { + LOCK.unlock(); + } + } + + /** + * May be overridden by Managers to perform processing while the Manager is being released and the + * lock is held. + */ + protected void releaseSub() { + } + + protected int getCount() { + return count; + } + + /** + * Called to signify that this Manager is no longer required by an Appender. + */ + public void release() { + LOCK.lock(); + try { + --count; + if (count <= 0) { + MAP.remove(name); + LOGGER.debug("Shutting down {} {}", this.getClass().getSimpleName(), getName()); + releaseSub(); + } + } finally { + LOCK.unlock(); + } + } + + /** + * Returns the name of the Manager. + * @return The name of the Manager. + */ + public String getName() { + return name; + } + + /** + * Provide a description of the content format supported by this Manager. Default implementation returns an empty + * (unspecified) Map. + * + * @return a Map of key/value pairs describing the Manager-specific content format, or an empty Map if no content + * format descriptors are specified. + */ + public Map<String, String> getContentFormat() { + return new HashMap<>(); + } + + protected void log(Level level, String message, Throwable throwable) { + Message m = LOGGER.getMessageFactory().newMessage("{} {} {}: {}", + getClass().getSimpleName(), getName(), message, throwable); + LOGGER.log(level, m, throwable); + } + + protected void logDebug(String message, Throwable throwable) { + log(Level.DEBUG, message, throwable); + } + + protected void logError(String message, Throwable throwable) { + log(Level.ERROR, message, throwable); + } + + protected void logWarn(String message, Throwable throwable) { + log(Level.WARN, message, throwable); + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3c84ef94/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java index 608f2e8..ae6a017 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; @@ -44,8 +45,14 @@ public class RollingFileManager extends FileManager { private long initialTime; private final PatternProcessor patternProcessor; private final Semaphore semaphore = new Semaphore(1); - private final TriggeringPolicy triggeringPolicy; - private final RolloverStrategy rolloverStrategy; + private volatile TriggeringPolicy triggeringPolicy; + private volatile RolloverStrategy rolloverStrategy; + + 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"); protected RollingFileManager(final String fileName, final String pattern, final OutputStream os, final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy, @@ -142,6 +149,17 @@ public class RollingFileManager extends FileManager { return patternProcessor; } + public void setTriggeringPolicy(final TriggeringPolicy triggeringPolicy) + { + triggeringPolicy.initialize(this); + triggeringPolicyUpdater.compareAndSet(this, this.triggeringPolicy, triggeringPolicy); + } + + public void setRolloverStrategy(final RolloverStrategy rolloverStrategy) + { + rolloverStrategyUpdater.compareAndSet(this, this.rolloverStrategy, rolloverStrategy); + } + /** * Returns the triggering policy. * @param <T> TriggeringPolicy type @@ -291,6 +309,24 @@ public class RollingFileManager extends FileManager { this.advertiseURI = advertiseURI; this.layout = layout; } + + public TriggeringPolicy getTriggeringPolicy() + { + return this.policy; + } + + public RolloverStrategy getRolloverStrategy() + { + return this.strategy; + } + } + + @Override + public void updateData(final Object data) + { + final FactoryData factoryData = (FactoryData) data; + setRolloverStrategy(factoryData.getRolloverStrategy()); + setTriggeringPolicy(factoryData.getTriggeringPolicy()); } /** http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3c84ef94/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java.orig ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java.orig b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java.orig new file mode 100644 index 0000000..608f2e8 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java.orig @@ -0,0 +1,343 @@ +/* + * 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.rolling; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.concurrent.Semaphore; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +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.util.Log4jThread; + +/** + * The Rolling File Manager. + */ +public class RollingFileManager extends FileManager { + + private static RollingFileManagerFactory factory = new RollingFileManagerFactory(); + + private long size; + private long initialTime; + private final PatternProcessor patternProcessor; + private final Semaphore semaphore = new Semaphore(1); + private final TriggeringPolicy triggeringPolicy; + private final RolloverStrategy rolloverStrategy; + + protected RollingFileManager(final String fileName, final String pattern, final OutputStream os, + final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy, + final RolloverStrategy rolloverStrategy, final String advertiseURI, + final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) { + super(fileName, os, append, false, advertiseURI, layout, bufferSize, writeHeader); + this.size = size; + this.initialTime = time; + this.triggeringPolicy = triggeringPolicy; + this.rolloverStrategy = rolloverStrategy; + this.patternProcessor = new PatternProcessor(pattern); + triggeringPolicy.initialize(this); + } + + /** + * Returns a RollingFileManager. + * @param fileName The file name. + * @param pattern The pattern for rolling file. + * @param append true if the file should be appended to. + * @param bufferedIO true if data should be buffered. + * @param policy The TriggeringPolicy. + * @param strategy The RolloverStrategy. + * @param advertiseURI the URI to use when advertising the file + * @param layout The Layout. + * @param bufferSize buffer size to use if bufferedIO is true + * @return A RollingFileManager. + */ + 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) { + + return (RollingFileManager) getManager(fileName, new FactoryData(pattern, append, + bufferedIO, policy, strategy, advertiseURI, layout, bufferSize), factory); + } + + @Override + protected synchronized void write(final byte[] bytes, final int offset, final int length) { + size += length; + super.write(bytes, offset, length); + } + + /** + * Returns the current size of the file. + * @return The size of the file in bytes. + */ + public long getFileSize() { + return size; + } + + /** + * Returns the time the file was created. + * @return The time the file was created. + */ + public long getFileTime() { + return initialTime; + } + + /** + * Determine if a rollover should occur. + * @param event The LogEvent. + */ + public synchronized void checkRollover(final LogEvent event) { + if (triggeringPolicy.isTriggeringEvent(event)) { + rollover(); + } + } + + public synchronized void rollover() { + 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 { + final OutputStream os = new FileOutputStream(getFileName(), isAppend()); + if (getBufferSize() > 0) { // negative buffer size means no buffering + setOutputStream(new BufferedOutputStream(os, getBufferSize())); + } else { + setOutputStream(os); + } + } + + /** + * Returns the pattern processor. + * @return The PatternProcessor. + */ + public PatternProcessor getPatternProcessor() { + return patternProcessor; + } + + /** + * Returns the triggering policy. + * @param <T> TriggeringPolicy type + * @return The TriggeringPolicy + */ + public <T extends TriggeringPolicy> T getTriggeringPolicy() { + // TODO We could parameterize this class with a TriggeringPolicy instead of type casting here. + return (T) this.triggeringPolicy; + } + + /** + * Returns the rollover strategy. + * @return The RolloverStrategy + */ + public RolloverStrategy getRolloverStrategy() { + return this.rolloverStrategy; + } + + private boolean rollover(final RolloverStrategy strategy) { + + try { + // Block until the asynchronous operation is completed. + semaphore.acquire(); + } catch (final InterruptedException e) { + logError("Thread interrupted while attempting to check rollover", e); + return false; + } + + boolean success = false; + Thread thread = null; + + try { + final RolloverDescription descriptor = strategy.rollover(this); + if (descriptor != null) { + writeFooter(); + close(); + if (descriptor.getSynchronous() != null) { + LOGGER.debug("RollingFileManager executing synchronous {}", descriptor.getSynchronous()); + try { + success = descriptor.getSynchronous().execute(); + } catch (final Exception ex) { + logError("caught error in synchronous task", ex); + } + } + + if (success && descriptor.getAsynchronous() != null) { + LOGGER.debug("RollingFileManager executing async {}", descriptor.getAsynchronous()); + thread = new Log4jThread(new AsyncAction(descriptor.getAsynchronous(), this)); + thread.start(); + } + return true; + } + return false; + } finally { + if (thread == null || !thread.isAlive()) { + semaphore.release(); + } + } + + } + + /** + * Performs actions asynchronously. + */ + private static class AsyncAction extends AbstractAction { + + private final Action action; + private final RollingFileManager manager; + + /** + * Constructor. + * @param act The action to perform. + * @param manager The manager. + */ + public AsyncAction(final Action act, final RollingFileManager manager) { + this.action = act; + this.manager = manager; + } + + /** + * Perform an action. + * + * @return true if action was successful. A return value of false will cause + * the rollover to be aborted if possible. + * @throws java.io.IOException if IO error, a thrown exception will cause the rollover + * to be aborted if possible. + */ + @Override + public boolean execute() throws IOException { + try { + return action.execute(); + } finally { + manager.semaphore.release(); + } + } + + /** + * Cancels the action if not already initialized or waits till completion. + */ + @Override + public void close() { + action.close(); + } + + /** + * Determines if action has been completed. + * + * @return true if action is complete. + */ + @Override + public boolean isComplete() { + return action.isComplete(); + } + } + + /** + * Factory data. + */ + private static class FactoryData { + private final String pattern; + private final boolean append; + private final boolean bufferedIO; + private final int bufferSize; + private final TriggeringPolicy policy; + private final RolloverStrategy strategy; + private final String advertiseURI; + private final Layout<? extends Serializable> layout; + + /** + * Create the data for the factory. + * @param pattern The pattern. + * @param append The append flag. + * @param bufferedIO The bufferedIO flag. + * @param advertiseURI + * @param layout The Layout. + * @param bufferSize the buffer size + */ + public FactoryData(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) { + this.pattern = pattern; + this.append = append; + this.bufferedIO = bufferedIO; + this.bufferSize = bufferSize; + this.policy = policy; + this.strategy = strategy; + this.advertiseURI = advertiseURI; + this.layout = layout; + } + } + + /** + * Factory to create a RollingFileManager. + */ + private static class RollingFileManagerFactory implements ManagerFactory<RollingFileManager, FactoryData> { + + /** + * Create the RollingFileManager. + * @param name The name of the entity to manage. + * @param data The data required to create the entity. + * @return a RollingFileManager. + */ + @Override + public RollingFileManager createManager(final String name, final FactoryData data) { + final File file = new File(name); + final File parent = file.getParentFile(); + if (null != parent && !parent.exists()) { + parent.mkdirs(); + } + // LOG4J2-1140: check writeHeader before creating the file + final boolean writeHeader = !data.append || !file.exists(); + try { + file.createNewFile(); + } catch (final IOException ioe) { + LOGGER.error("Unable to create file " + name, ioe); + return null; + } + final long size = data.append ? file.length() : 0; + + OutputStream os; + try { + os = new FileOutputStream(name, data.append); + int bufferSize = data.bufferSize; + if (data.bufferedIO) { + os = new BufferedOutputStream(os, bufferSize); + } else { + bufferSize = -1; // negative buffer size signals bufferedIO was configured false + } + final long time = file.lastModified(); // LOG4J2-531 create file first so time has valid value + return new RollingFileManager(name, data.pattern, os, data.append, size, time, data.policy, + data.strategy, data.advertiseURI, data.layout, bufferSize, writeHeader); + } catch (final FileNotFoundException ex) { + LOGGER.error("FileManager (" + name + ") " + ex); + } + return null; + } + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3c84ef94/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java index dc3261e..b732feb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java @@ -247,6 +247,23 @@ public class RollingRandomAccessFileManager extends RollingFileManager { this.advertiseURI = advertiseURI; this.layout = layout; } + + public TriggeringPolicy getTriggeringPolicy() + { + return this.policy; + } + + public RolloverStrategy getRolloverStrategy() + { + return this.strategy; + } } + @Override + public void updateData(final Object data) + { + final FactoryData factoryData = (FactoryData) data; + setRolloverStrategy(factoryData.getRolloverStrategy()); + setTriggeringPolicy(factoryData.getTriggeringPolicy()); + } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3c84ef94/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java.orig ---------------------------------------------------------------------- diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java.orig b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java.orig new file mode 100644 index 0000000..dc3261e --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java.orig @@ -0,0 +1,252 @@ +/* + * 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.rolling; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.nio.ByteBuffer; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.appender.AppenderLoggingException; +import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.util.NullOutputStream; + +/** + * Extends RollingFileManager but instead of using a buffered output stream, this class uses a {@code ByteBuffer} and a + * {@code RandomAccessFile} to do the I/O. + */ +public class RollingRandomAccessFileManager extends RollingFileManager { + /** + * The default buffer size. + */ + public static final int DEFAULT_BUFFER_SIZE = 256 * 1024; + + private static final RollingRandomAccessFileManagerFactory FACTORY = new RollingRandomAccessFileManagerFactory(); + + private final boolean isImmediateFlush; + private RandomAccessFile randomAccessFile; + private final ByteBuffer buffer; + private final ThreadLocal<Boolean> isEndOfBatch = new ThreadLocal<>(); + + public RollingRandomAccessFileManager(final RandomAccessFile raf, final String fileName, final String pattern, + final OutputStream os, final boolean append, final boolean immediateFlush, final int bufferSize, + final long size, final long time, final TriggeringPolicy policy, final RolloverStrategy strategy, + final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader) { + super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI, layout, bufferSize, + writeHeader); + this.isImmediateFlush = immediateFlush; + this.randomAccessFile = raf; + isEndOfBatch.set(Boolean.FALSE); + this.buffer = ByteBuffer.allocate(bufferSize); + writeHeader(); + } + + /** + * Writes the layout's header to the file if it exists. + */ + private void writeHeader() { + if (layout == null) { + return; + } + final byte[] header = layout.getHeader(); + if (header == null) { + return; + } + try { + // write to the file, not to the buffer: the buffer may not be empty + randomAccessFile.write(header, 0, header.length); + } catch (final IOException e) { + logError("unable to write header", e); + } + } + + public static RollingRandomAccessFileManager getRollingRandomAccessFileManager(final String fileName, + final String filePattern, final boolean isAppend, final boolean immediateFlush, final int bufferSize, + final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI, + final Layout<? extends Serializable> layout) { + return (RollingRandomAccessFileManager) getManager(fileName, new FactoryData(filePattern, isAppend, + immediateFlush, bufferSize, policy, strategy, advertiseURI, layout), FACTORY); + } + + public Boolean isEndOfBatch() { + return isEndOfBatch.get(); + } + + public void setEndOfBatch(final boolean endOfBatch) { + this.isEndOfBatch.set(Boolean.valueOf(endOfBatch)); + } + + @Override + protected synchronized void write(final byte[] bytes, int offset, int length) { + super.write(bytes, offset, length); // writes to dummy output stream, needed to track file size + + int chunk = 0; + do { + if (length > buffer.remaining()) { + flush(); + } + chunk = Math.min(length, buffer.remaining()); + buffer.put(bytes, offset, chunk); + offset += chunk; + length -= chunk; + } while (length > 0); + + if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) { + flush(); + } + } + + @Override + protected void createFileAfterRollover() throws IOException { + this.randomAccessFile = new RandomAccessFile(getFileName(), "rw"); + if (isAppend()) { + randomAccessFile.seek(randomAccessFile.length()); + } + writeHeader(); + } + + @Override + public synchronized void flush() { + buffer.flip(); + try { + randomAccessFile.write(buffer.array(), 0, buffer.limit()); + } catch (final IOException ex) { + final String msg = "Error writing to RandomAccessFile " + getName(); + throw new AppenderLoggingException(msg, ex); + } + buffer.clear(); + } + + @Override + public synchronized void close() { + flush(); + try { + randomAccessFile.close(); + } catch (final IOException e) { + logError("unable to close RandomAccessFile", e); + } + } + + /** + * Returns the buffer capacity. + * + * @return the buffer size + */ + @Override + public int getBufferSize() { + return buffer.capacity(); + } + + /** + * Factory to create a RollingRandomAccessFileManager. + */ + private static class RollingRandomAccessFileManagerFactory implements + ManagerFactory<RollingRandomAccessFileManager, FactoryData> { + + /** + * Create the RollingRandomAccessFileManager. + * + * @param name The name of the entity to manage. + * @param data The data required to create the entity. + * @return a RollingFileManager. + */ + @Override + public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) { + final File file = new File(name); + final File parent = file.getParentFile(); + if (null != parent && !parent.exists()) { + parent.mkdirs(); + } + + if (!data.append) { + file.delete(); + } + final long size = data.append ? file.length() : 0; + final long time = file.exists() ? file.lastModified() : System.currentTimeMillis(); + + final boolean writeHeader = !data.append || !file.exists(); + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(name, "rw"); + if (data.append) { + final long length = raf.length(); + LOGGER.trace("RandomAccessFile {} seek to {}", name, length); + raf.seek(length); + } else { + LOGGER.trace("RandomAccessFile {} set length to 0", name); + raf.setLength(0); + } + return new RollingRandomAccessFileManager(raf, name, data.pattern, NullOutputStream.NULL_OUTPUT_STREAM, + data.append, data.immediateFlush, data.bufferSize, size, time, data.policy, data.strategy, + data.advertiseURI, data.layout, writeHeader); + } catch (final IOException ex) { + LOGGER.error("Cannot access RandomAccessFile {}) " + ex); + if (raf != null) { + try { + raf.close(); + } catch (final IOException e) { + LOGGER.error("Cannot close RandomAccessFile {}", name, e); + } + } + } + return null; + } + } + + /** + * Factory data. + */ + private static class FactoryData { + private final String pattern; + private final boolean append; + private final boolean immediateFlush; + private final int bufferSize; + private final TriggeringPolicy policy; + private final RolloverStrategy strategy; + private final String advertiseURI; + private final Layout<? extends Serializable> layout; + + /** + * Create the data for the factory. + * + * @param pattern The pattern. + * @param append The append flag. + * @param immediateFlush + * @param bufferSize + * @param policy + * @param strategy + * @param advertiseURI + * @param layout + */ + public FactoryData(final String pattern, final boolean append, final boolean immediateFlush, + final int bufferSize, final TriggeringPolicy policy, final RolloverStrategy strategy, + final String advertiseURI, final Layout<? extends Serializable> layout) { + this.pattern = pattern; + this.append = append; + this.immediateFlush = immediateFlush; + this.bufferSize = bufferSize; + this.policy = policy; + this.strategy = strategy; + this.advertiseURI = advertiseURI; + this.layout = layout; + } + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/3c84ef94/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 1ae3f64..b95c6d8 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -24,6 +24,12 @@ </properties> <body> <release version="2.5" date="2015-MM-DD" description="GA Release 2.5"> + <action issue="LOG4J2-89" dev="rgoers" type="add"> + Allow rollover to occur at any time. Add CronTriggeringPolicy + </action> + <action issue="LOG4J2-381" dev="rgoers" type="fix" due-to="Anthony Baldocchi"> + Allow triggering policy and rollover strategy to be modified during reconfiguration. + </action> <action issue="LOG4J2-1136" dev="rgoers" type="add"> Add support for JSR 223 scripts in filters and the PatternSelector. </action>
