This is an automated email from the ASF dual-hosted git repository. rgoers pushed a commit to branch release-2.x in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/release-2.x by this push: new a8e654a LOG4J2-2923 - Add Rollover Listener to aid in unit test validation. a8e654a is described below commit a8e654af511d04664cc2b11fdda273883cca9a16 Author: Ralph Goers <rgo...@apache.org> AuthorDate: Tue Mar 2 20:58:39 2021 -0700 LOG4J2-2923 - Add Rollover Listener to aid in unit test validation. --- .../core/appender/rolling/RollingFileManager.java | 39 ++++++++++ .../core/appender/rolling/RolloverListener.java | 37 +++++++++ .../RollingDirectSizeTimeNewDirectoryTest.java | 90 +++++++++------------- src/changes/changes.xml | 3 + 4 files changed, 116 insertions(+), 53 deletions(-) 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 77693f0..dc7ca76 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 @@ -29,6 +29,7 @@ import java.nio.file.attribute.FileTime; import java.util.Collection; import java.util.Date; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadPoolExecutor; @@ -71,6 +72,7 @@ public class RollingFileManager extends FileManager { private volatile boolean initialized; private volatile String fileName; private final boolean directWrite; + private final CopyOnWriteArrayList<RolloverListener> rolloverListeners = new CopyOnWriteArrayList<>(); /* This executor pool will create a new Thread for every work async action to be performed. Using it allows us to make sure all the Threads are completed when the Manager is stopped. */ @@ -218,6 +220,22 @@ public class RollingFileManager extends FileManager { } /** + * Add a RolloverListener. + * @param listener The RolloverListener. + */ + public void addRolloverListener(RolloverListener listener) { + rolloverListeners.add(listener); + } + + /** + * Remove a RolloverListener. + * @param listener The RolloverListener. + */ + public void removeRolloverListener(RolloverListener listener) { + rolloverListeners.remove(listener); + } + + /** * Returns the name of the File being managed. * @return The name of the File being managed. */ @@ -353,6 +371,17 @@ public class RollingFileManager extends FileManager { if (!hasOutputStream() && !isCreateOnDemand() && !isDirectWrite()) { return; } + String currentFileName = fileName; + if (rolloverListeners.size() > 0) { + for (RolloverListener listener : rolloverListeners) { + try { + listener.rolloverTriggered(currentFileName); + } catch (Exception ex) { + LOGGER.warn("Rollover Listener {} failed with {}: {}", listener.getClass().getSimpleName(), + ex.getClass().getName(), ex.getMessage()); + } + } + } if (rollover(rolloverStrategy)) { try { size = 0; @@ -362,6 +391,16 @@ public class RollingFileManager extends FileManager { logError("Failed to create file after rollover", e); } } + if (rolloverListeners.size() > 0) { + for (RolloverListener listener : rolloverListeners) { + try { + listener.rolloverComplete(currentFileName); + } catch (Exception ex) { + LOGGER.warn("Rollover Listener {} failed with {}: {}", listener.getClass().getSimpleName(), + ex.getClass().getName(), ex.getMessage()); + } + } + } } protected void createFileAfterRollover() throws IOException { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverListener.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverListener.java new file mode 100644 index 0000000..de3a298 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RolloverListener.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * Implementations of this interface that are registered with the RollingFileManager will be notified before and + * after a rollover occurs. This is a synchronous call so Listeners should exit the methods as fast as possible. + * It is recommended that they simply notify some other already active thread. + */ +public interface RolloverListener { + + /** + * Called before rollover. + * @param fileName The name of the file rolling over. + */ + void rolloverTriggered(String fileName); + + /** + * Called after rollover. + * @param fileName The name of the file rolling over. + */ + void rolloverComplete(String fileName); +} diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSizeTimeNewDirectoryTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSizeTimeNewDirectoryTest.java index 4bf667c..dbdd0d8 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSizeTimeNewDirectoryTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingDirectSizeTimeNewDirectoryTest.java @@ -16,27 +16,33 @@ */ package org.apache.logging.log4j.core.appender.rolling; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.filefilter.TrueFileFilter; -import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.appender.RollingFileAppender; import org.apache.logging.log4j.junit.LoggerContextRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; -public class RollingDirectSizeTimeNewDirectoryTest { +import static org.junit.Assert.assertTrue; + +/** + * This test attempts to validate that logging rolls when the file size exceeds 5KB or every second. + * When the file rolls by time it should create a new directory. When rolling by size it should + * create multiple files per directory. + */ +public class RollingDirectSizeTimeNewDirectoryTest implements RolloverListener { private static final String CONFIG = "log4j-rolling-size-time-new-directory.xml"; // Note that the path is hardcoded in the configuration! private static final String DIR = "target/rolling-size-time-new-directory"; + private static final String FILESEP = System.getProperty("file.separator"); public static LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG); @@ -44,9 +50,12 @@ public class RollingDirectSizeTimeNewDirectoryTest { @Rule public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR); + private Map<String, AtomicInteger> rolloverFiles = new HashMap<>(); + @Test public void streamClosedError() throws Exception { - + ((RollingFileAppender) loggerContextRule.getAppender("RollingFile")).getManager() + .addRolloverListener(this); final Logger logger = loggerContextRule.getLogger(RollingDirectSizeTimeNewDirectoryTest.class); for (int i = 0; i < 1000; i++) { @@ -57,50 +66,25 @@ public class RollingDirectSizeTimeNewDirectoryTest { logger.info("nHq6p9kgfvWfjzDRYbZp"); } - final File logDir = new File(DIR); - final File[] logFolders = logDir.listFiles(); - assertNotNull("Not a folder: " + logDir, logFolders); - Arrays.sort(logFolders); - - try { - final int minExpectedLogFolderCount = 2; - assertTrue( - "was expecting at least " + minExpectedLogFolderCount + " folders, " + "found " + logFolders.length, - logFolders.length >= minExpectedLogFolderCount); - - for (int logFolderIndex = 0; logFolderIndex < logFolders.length; ++logFolderIndex) { - - File logFolder = logFolders[logFolderIndex]; - File[] logFiles = logFolder.listFiles(); - assertNotNull("Not a folder: " + logFolder, logFiles); - Arrays.sort(logFiles); - if (logFolderIndex == 0 && logFolders.length > 0 && logFiles.length == 0) { - // In a slow execution period, it is possible for the initial directory to be - // empty because it has not received events yet. If this is the case, then the - // next directory MUST contain at least one log file. - } else { - assertTrue("no files found in folder: " + logFolder, ArrayUtils.isNotEmpty(logFiles)); - } - final int minExpectedLogFileCount = 2; - if (logFolderIndex > 0 && logFolderIndex < logFolders.length - 1) { - assertTrue( - "was expecting at least " + minExpectedLogFileCount + " files, " + "found " - + logFiles.length + ": " + Arrays.toString(logFiles), - logFiles.length >= minExpectedLogFileCount); - } - } - - } catch (AssertionError error) { - System.out.format("log directory (%s) contents:%n", DIR); - int totalFileCount = 0; - for (final File file : FileUtils.listFilesAndDirs(logDir, TrueFileFilter.TRUE, TrueFileFilter.TRUE)) { - totalFileCount++; - System.out.format("-> %s (%d)%n", file, file.length()); - } - System.out.format("total file count: %d%n", totalFileCount); - throw new AssertionError("check failure", error); - } + assertTrue("A time based rollover did not occur", rolloverFiles.size() > 1); + int maxFiles = Collections.max(rolloverFiles.values(), Comparator.comparing(AtomicInteger::get)).get(); + assertTrue("No size based rollovers occurred", maxFiles > 1); + } + + @Override + public void rolloverTriggered(String fileName) { } + @Override + public void rolloverComplete(String fileName) { + String[] parts = fileName.split(FILESEP); + if (parts.length < 4) { + System.err.println("Invalid or missing filename: " + fileName); + return; + } + AtomicInteger fileCount = rolloverFiles.computeIfAbsent(parts[2], k -> new AtomicInteger(0)); + fileCount.incrementAndGet(); + } } + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0ad7b0f..e6b20fe 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -101,6 +101,9 @@ Support stack trace truncation in JsonTemplateLayout. </action> <!-- UPDATES --> + <action issue="LOG4J2-2923" dev="rgoers" type="upd"> + Add Rollover Listener to aid in unit test validation. + </action> <action issue="LOG4J2-2893" dev="rgoers" type="update"> Allow reconfiguration when Log4j 1 configuration files are updated. </action>