You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by sw...@apache.org on 2026/10/16 02:01:41 UTC

(logging-log4cxx) branch master updated: Lazily create logging files to avoid creating empty files (#775)

This is an automated email from the ASF dual-hosted git repository.

swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git


The following commit(s) were added to refs/heads/master by this push:
     new 36347c0a Lazily create logging files to avoid creating empty files (#775)
36347c0a is described below

commit 36347c0a48a059ac50f8dcde197fc14541a5d0be
Author: niqiangpro-cell <ni...@gmail.com>
AuthorDate: Fri Oct 16 04:01:35 2026 +0200

    Lazily create logging files to avoid creating empty files (#775)
    
    * Add FileAppender documentation and a change report entry
---
 src/main/cpp/fileappender.cpp                      | 62 ++++++++++++++++++----
 src/main/cpp/fileoutputstream.cpp                  | 50 +++++++++++++----
 src/main/cpp/multiprocessrollingfileappender.cpp   | 32 +++--------
 src/main/include/log4cxx/fileappender.h            | 30 +++++++++--
 .../include/log4cxx/helpers/fileoutputstream.h     | 12 ++++-
 .../include/log4cxx/private/fileappender_priv.h    |  4 ++
 src/site/markdown/change-report-gh.md              |  3 ++
 src/test/cpp/fileappendertest.cpp                  | 26 +++++++++
 src/test/cpp/hierarchythresholdtestcase.cpp        | 27 +++++++---
 src/test/cpp/rolling/timebasedrollingtest.cpp      | 33 ++++++++----
 10 files changed, 212 insertions(+), 67 deletions(-)

diff --git a/src/main/cpp/fileappender.cpp b/src/main/cpp/fileappender.cpp
index 9d5b3954..1ff956b3 100644
--- a/src/main/cpp/fileappender.cpp
+++ b/src/main/cpp/fileappender.cpp
@@ -143,6 +143,11 @@ void FileAppender::setOption(const LogString& option,
 		std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
 		_priv->bufferedSeconds = OptionConverter::toInt(value, 0);
 	}
+	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("LAZYFILECREATION"), LOG4CXX_STR("lazyfilecreation")))
+	{
+		std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
+		_priv->lazyFileCreation = OptionConverter::toBoolean(value, false);
+	}
 	else
 	{
 		WriterAppender::setOption(option, value);
@@ -296,11 +301,7 @@ void FileAppender::FileAppenderPriv::open()
 
 	OutputStreamPtr outStream;
 
-	try
-	{
-		outStream = std::make_shared<FileOutputStream>(this->targetFile.getPath(), this->fileAppend);
-	}
-	catch (IOException&)
+	if (this->lazyFileCreation)
 	{
 		LogString parentName = this->targetFile.getParent();
 
@@ -308,19 +309,47 @@ void FileAppender::FileAppenderPriv::open()
 		{
 			File parentDir(parentName);
 
-			if (!parentDir.exists() && parentDir.mkdirs())
+			if (!parentDir.exists())
+			{
+				LogLog::debug(LOG4CXX_STR("Creating dirs ") + parentDir.getPath());
+				// The file is not opened here, so a failure must be reported now.
+				if (!parentDir.mkdirs() && !parentDir.exists())
+				{
+					throw IOException(LOG4CXX_STR("Unable to create directory ") + parentDir.getPath());
+				}
+			}
+		}
+
+		outStream = std::make_shared<FileOutputStream>(this->targetFile.getPath(), this->fileAppend, true);
+	}
+	else
+	{
+		try
+		{
+			outStream = std::make_shared<FileOutputStream>(this->targetFile.getPath(), this->fileAppend);
+		}
+		catch (IOException&)
+		{
+			LogString parentName = this->targetFile.getParent();
+
+			if (!parentName.empty())
 			{
-				outStream = std::make_shared<FileOutputStream>(this->targetFile.getPath(), this->fileAppend);
+				File parentDir(parentName);
+
+				if (!parentDir.exists() && parentDir.mkdirs())
+				{
+					outStream = std::make_shared<FileOutputStream>(this->targetFile.getPath(), this->fileAppend);
+				}
+				else
+				{
+					throw;
+				}
 			}
 			else
 			{
 				throw;
 			}
 		}
-		else
-		{
-			throw;
-		}
 	}
 
 
@@ -388,3 +417,14 @@ bool FileAppender::getAppend() const
 	return _priv->fileAppend;
 }
 
+void FileAppender::setLazyFileCreation(bool newValue)
+{
+	std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
+	_priv->lazyFileCreation = newValue;
+}
+
+bool FileAppender::getLazyFileCreation() const
+{
+	return _priv->lazyFileCreation;
+}
+
diff --git a/src/main/cpp/fileoutputstream.cpp b/src/main/cpp/fileoutputstream.cpp
index 0264b61f..fae252f2 100644
--- a/src/main/cpp/fileoutputstream.cpp
+++ b/src/main/cpp/fileoutputstream.cpp
@@ -27,30 +27,45 @@ using namespace LOG4CXX_NS::helpers;
 
 struct FileOutputStream::FileOutputStreamPrivate
 {
-	FileOutputStreamPrivate(const LogString& filename)
-		: path(filename)
+	FileOutputStreamPrivate(const LogString& filename, bool append)
+		: path(filename), logAppend(append)
 		{}
 
 	File path;
 	Pool pool;
 	apr_file_t* fileptr{ nullptr };
-	void open(bool append);
+	bool logAppend;
+	void open();
+	void openUnlessLazy(bool lazyOpen);
 };
 
 IMPLEMENT_LOG4CXX_OBJECT(FileOutputStream)
 
-FileOutputStream::FileOutputStream(const LogString& filename, bool append)
-	: m_priv(std::make_unique<FileOutputStreamPrivate>(filename))
+FileOutputStream::FileOutputStream(const LogString& filename, bool append, bool lazyOpen)
+	: m_priv(std::make_unique<FileOutputStreamPrivate>(filename, append))
 {
-	m_priv->open(append);
+	m_priv->openUnlessLazy(lazyOpen);
 }
 
-void FileOutputStream::FileOutputStreamPrivate::open(bool append)
+void FileOutputStream::FileOutputStreamPrivate::openUnlessLazy(bool lazyOpen)
+{
+	if (!lazyOpen)
+	{
+		this->open();
+	}
+	else if (!this->logAppend && this->path.exists())
+	{
+		// Truncate the file (i.e. remove its contents)
+		this->open();
+	}
+}
+
+void FileOutputStream::FileOutputStreamPrivate::open()
 {
 	apr_fileperms_t perm = APR_OS_DEFAULT;
 	apr_int32_t flags = APR_WRITE | APR_CREATE;
 
-	if (append)
+	if (this->logAppend)
 	{
 		flags |= APR_APPEND;
 	}
@@ -72,6 +87,7 @@ FileOutputStream::~FileOutputStream()
 	if (m_priv->fileptr)
 	{
 		apr_file_close(m_priv->fileptr);
+		m_priv->fileptr = nullptr;
 	}
 }
 
@@ -98,7 +114,12 @@ void FileOutputStream::write( ByteBuffer& buf )
 {
 	if (m_priv->fileptr == NULL)
 	{
-		throw NullPointerException(LOG4CXX_STR("FileOutputStream"));
+		m_priv->open();
+
+		if (m_priv->fileptr == nullptr)
+		{
+			throw NullPointerException(LOG4CXX_STR("FileOutputStream"));
+		}
 	}
 
 	size_t nbytes = buf.remaining();
@@ -117,6 +138,17 @@ void FileOutputStream::write( ByteBuffer& buf )
 }
 
 apr_file_t* FileOutputStream::getFilePtr() const{
+	if (!m_priv->fileptr)
+	{
+		try
+		{
+			m_priv->open();
+		}
+		catch (IOException&)
+		{
+			// write() will open (and report any failure) again
+		}
+	}
 	return m_priv->fileptr;
 }
 
diff --git a/src/main/cpp/multiprocessrollingfileappender.cpp b/src/main/cpp/multiprocessrollingfileappender.cpp
index 92add2f2..12a2a029 100644
--- a/src/main/cpp/multiprocessrollingfileappender.cpp
+++ b/src/main/cpp/multiprocessrollingfileappender.cpp
@@ -57,7 +57,7 @@ struct MultiprocessRollingFileAppender::MultiprocessRollingFileAppenderPriv
 			apr_file_close(lock_file);
 	}
 
-	apr_file_t* log_file = NULL;
+	FileOutputStreamPtr fileOutputStream;
 	apr_file_t* lock_file = NULL;
 
 public: // Support classes
@@ -159,12 +159,6 @@ void MultiprocessRollingFileAppender::activateOptions(  )
 {
 	if (_priv->activateOptions())
 	{
-		// FileAppenderPriv::activate() closes any existing writer,
-		// destroying the FileOutputStream (and the pool) that owns the cached
-		// apr_file_t, before opening the new file (which may fail).  Clear the
-		// cache first so a failed open cannot leave it dangling;
-		// createWriter() re-caches the handle on success.
-		_priv->log_file = NULL;
 		_priv->activate();
 	}
 
@@ -190,13 +184,13 @@ bool MultiprocessRollingFileAppender::isRolloverCheckNeeded()
  */
 bool MultiprocessRollingFileAppender::isAlreadyRolled(const LogString& fileName, size_t* pSize)
 {
-	if( !_priv->log_file )
+	if( !_priv->fileOutputStream || !_priv->fileOutputStream->getFilePtr() )
 		return false;
 	apr_int32_t wantedInfo = APR_FINFO_IDENT;
 	if (pSize)
 		wantedInfo |= APR_FINFO_SIZE;
 	apr_finfo_t finfo1;
-	apr_status_t st1 = apr_file_info_get(&finfo1, wantedInfo, _priv->log_file);
+	apr_status_t st1 = apr_file_info_get(&finfo1, wantedInfo, _priv->fileOutputStream->getFilePtr());
 
 	if (st1 != APR_SUCCESS)
 		LogLog::warn(LOG4CXX_STR("apr_file_info_get failed"));
@@ -229,11 +223,11 @@ bool MultiprocessRollingFileAppender::isAlreadyRolled(const LogString& fileName,
  */
 bool MultiprocessRollingFileAppender::getCurrentFileSize(size_t* pSize)
 {
-	if( !_priv->log_file )
+	if( !_priv->fileOutputStream || !_priv->fileOutputStream->getFilePtr() )
 		return false;
 	apr_int32_t wantedInfo = APR_FINFO_SIZE;
 	apr_finfo_t finfo;
-	if (apr_file_info_get(&finfo, wantedInfo, _priv->log_file) != APR_SUCCESS)
+	if (apr_file_info_get(&finfo, wantedInfo, _priv->fileOutputStream->getFilePtr()) != APR_SUCCESS)
 	{
 		LogLog::warn(LOG4CXX_STR("apr_file_info_get failed"));
 		return false;
@@ -289,13 +283,6 @@ bool MultiprocessRollingFileAppender::synchronizedRollover(const TriggeringPolic
 		else if (auto rollover1 = _priv->rollingPolicy->rollover(fileName, getAppend()))
 		{
 			_priv->close();
-			// The cached apr_file_t was allocated from the pool of the
-			// FileOutputStream that close() just destroyed.  Clear it now:
-			// any failure (e.g. ENOSPC/EACCES opening the new active file
-			// throws, and subAppend merely logs the exception) would leave a
-			// dangling handle that isAlreadyRolled()/getCurrentFileSize()
-			// pass to apr_file_info_get() on the next append.
-			_priv->log_file = NULL;
 			if (rollover1->getActiveFileName() == fileName)
 			{
 				bool success = true; // A synchronous action is not required
@@ -391,16 +378,11 @@ bool MultiprocessRollingFileAppender::synchronizedRollover(const TriggeringPolic
 void MultiprocessRollingFileAppender::reopenFile(const LogString& fileName)
 {
 	_priv->close();
-	// Clear the cached apr_file_t: it was allocated from the pool of the
-	// FileOutputStream that close() just destroyed, and the FileOutputStream
-	// constructor below throws on open failure (leaving this appender without
-	// a writer until a later reopen succeeds).
-	_priv->log_file = NULL;
 #if USING_ROLLOVER_REQUIRED_CHECK_IS_FASTER
 	if (auto pTimeBased = LOG4CXX_NS::cast<TimeBasedRollingPolicy>(_priv->rollingPolicy))
 		pTimeBased->loadLastFileName();
 #endif
-	OutputStreamPtr os = std::make_shared<FileOutputStream>(fileName, true);
+	OutputStreamPtr os = std::make_shared<FileOutputStream>(fileName, true, _priv->lazyFileCreation);
 	WriterPtr newWriter(_priv->createWriter(os));
 	setFile(fileName);
 	_priv->setWriter(newWriter);
@@ -446,7 +428,7 @@ WriterPtr MultiprocessRollingFileAppender::MultiprocessRollingFileAppenderPriv::
 {
 	auto fos = LOG4CXX_NS::cast<FileOutputStream>(os);
 	if( fos )
-		this->log_file = fos->getFilePtr();
+		this->fileOutputStream = fos;
 	else
 	{
 		LogString msg(LOG4CXX_STR("Can't cast stream to FileOutputStream"));
diff --git a/src/main/include/log4cxx/fileappender.h b/src/main/include/log4cxx/fileappender.h
index 06e76b9b..5743b167 100644
--- a/src/main/include/log4cxx/fileappender.h
+++ b/src/main/include/log4cxx/fileappender.h
@@ -34,6 +34,10 @@ class Pool;
 /**
 *  FileAppender appends log events to a file.
 *
+* The file is opened/created immediately by #activateOptions()
+* unless the <code>LazyFileCreation</code> option is set <code>true</code>,
+* in which case the file is opened/created when the first spi::LoggingEvent is added.
+*
 *  Uses a background thread to periodically flush the output buffer
 *  when <code>BufferedIO</code> option is set <code>true</code>.
 *  Use the <code>BufferedSeconds</code> option to control the frequency,
@@ -118,11 +122,13 @@ class LOG4CXX_EXPORT FileAppender : public WriterAppender
 		/**
 		\copybrief WriterAppender::activateOptions()
 
-		Sets and <i>opens</i> the file where the log output will
-		go. The specified file must be writable.
+		When <code>LazyFileCreation</code> and <code>Append</code> are both set to <code>true</code>,
+		the file is opened/created when the first spi::LoggingEvent is added,
+		otherwise #activateOptions will open/create the file.
 
-		If there was already an opened file, then the previous file
-		is closed first.
+		When <code>BufferedIO</code> is <code>true</code> and
+		<code>BufferedSeconds</code> is greater than zero,
+		a background task is started that will periodically flush the buffer.
 		*/
 		void activateOptions(  ) override;
 
@@ -135,6 +141,7 @@ class LOG4CXX_EXPORT FileAppender : public WriterAppender
 		Append | True,False | True |
 		BufferedIO | True,False | False |
 		BufferedSeconds | {any} | 5 |
+		LazyFileCreation | True,False | False |
 		ImmediateFlush | True,False | False |
 		BufferSize | (\ref fileSz1 "1") | 8 KB |
 
@@ -210,6 +217,21 @@ class LOG4CXX_EXPORT FileAppender : public WriterAppender
 		*/
 		void setBufferedSeconds(int newValue);
 
+		/**
+		Create the file when the first message is written (rather than in #activateOptions)
+		if \c newValue is true, so that no empty file is created when nothing is logged.
+		The default is false.
+
+		Note: Behavior change occurs when
+		#activateOptions is called, not when the options are set.
+		*/
+		void setLazyFileCreation(bool newValue);
+
+		/**
+		Get the value of the <b>LazyFileCreation</b> option.
+		*/
+		bool getLazyFileCreation() const;
+
 		/**
 		 *   Replaces double backslashes with single backslashes
 		 *   for compatibility with paths from earlier XML configurations files.
diff --git a/src/main/include/log4cxx/helpers/fileoutputstream.h b/src/main/include/log4cxx/helpers/fileoutputstream.h
index f9b70247..24430ec1 100644
--- a/src/main/include/log4cxx/helpers/fileoutputstream.h
+++ b/src/main/include/log4cxx/helpers/fileoutputstream.h
@@ -30,6 +30,10 @@ namespace helpers
 
 /**
 *   OutputStream implemented on top of APR file IO.
+*
+*   This class assumes a single active thread: the file is opened lazily
+*   without synchronization.  The guarantee is provided by the mutex
+*   held in AppenderSkeleton::doAppend.
 */
 class LOG4CXX_EXPORT FileOutputStream : public OutputStream
 {
@@ -43,7 +47,13 @@ class LOG4CXX_EXPORT FileOutputStream : public OutputStream
 		LOG4CXX_CAST_ENTRY_CHAIN(OutputStream)
 		END_LOG4CXX_CAST_MAP()
 
-		FileOutputStream(const LogString& filename, bool append = false);
+		/**
+		Open \c filename unless \c lazyOpen is true,
+		in which case the file is opened on first write
+		(or when #getFilePtr is called)
+		and an existing file is truncated immediately if \c append is false.
+		*/
+		FileOutputStream(const LogString& filename, bool append = false, bool lazyOpen = false);
 		virtual ~FileOutputStream();
 
 		using OutputStream::close;
diff --git a/src/main/include/log4cxx/private/fileappender_priv.h b/src/main/include/log4cxx/private/fileappender_priv.h
index 986c5bbe..5100f494 100644
--- a/src/main/include/log4cxx/private/fileappender_priv.h
+++ b/src/main/include/log4cxx/private/fileappender_priv.h
@@ -70,6 +70,10 @@ struct FileAppender::FileAppenderPriv : public WriterAppender::WriterAppenderPri
 	*/
 	int bufferedSeconds{ 5 };
 
+	/**
+	Create the file when the first message is written? */
+	bool lazyFileCreation{ false };
+
 	/**
 	Control block shared with the periodic output buffer flush task.
 	The flush task only reaches this appender through the control block,
diff --git a/src/site/markdown/change-report-gh.md b/src/site/markdown/change-report-gh.md
index 9c07e863..815d99d7 100644
--- a/src/site/markdown/change-report-gh.md
+++ b/src/site/markdown/change-report-gh.md
@@ -70,6 +70,9 @@ Support remains for 'LOG4CXX_CHAR=wchar_t' for applications using std::wstring.
 
 Release 2.0.0 includes the following new features:
 
+* A new log4cxx::FileAppender option to delay log file creation until an event is added.
+   \[[#775](https://github.com/apache/logging-log4cxx/pull/775)\]
+
 * A new log4cxx::helpers::FileWatchdog::startWatching method to create a periodic task without a needing to sub-class.
    \[[#769](https://github.com/apache/logging-log4cxx/pull/769)\]
 
diff --git a/src/test/cpp/fileappendertest.cpp b/src/test/cpp/fileappendertest.cpp
index d2a35bcb..790d0729 100644
--- a/src/test/cpp/fileappendertest.cpp
+++ b/src/test/cpp/fileappendertest.cpp
@@ -63,6 +63,7 @@ LOGUNIT_CLASS(FileAppenderTest)
 {
 	LOGUNIT_TEST_SUITE(FileAppenderTest);
 	LOGUNIT_TEST(testDirectoryCreation);
+	LOGUNIT_TEST(testLazyFileCreation);
 	LOGUNIT_TEST(testgetSetThreshold);
 	LOGUNIT_TEST(testIsAsSevereAsThreshold);
 	LOGUNIT_TEST(testFlushTaskRemovedAfterRename);
@@ -102,6 +103,31 @@ public:
 		LOGUNIT_ASSERT(File(LOG4CXX_STR("output/newdir/temp.log")).exists());
 	}
 
+	/**
+	 * Tests that the LazyFileCreation option defers file creation until the first write.
+	 */
+	void testLazyFileCreation()
+	{
+		LogString fileName(LOG4CXX_STR("output/lazydir/lazy.log"));
+		File(fileName).deleteFile();
+		File(LOG4CXX_STR("output/lazydir")).deleteFile();
+
+		FileAppenderPtr wa(new FileAppender());
+		wa->setFile(fileName);
+		wa->setLazyFileCreation(true);
+		wa->setLayout(PatternLayoutPtr(new PatternLayout(LOG4CXX_STR("%m%n"))));
+		wa->activateOptions();
+		LOGUNIT_ASSERT(!File(fileName).exists());
+
+		auto logger = Logger::getLogger(LOG4CXX_STR("testLazyFileCreation"));
+		logger->addAppender(wa);
+		logger->setAdditivity(false);
+		LOG4CXX_INFO(logger, "trigger lazy file creation");
+		logger->removeAppender(wa);
+
+		LOGUNIT_ASSERT(File(fileName).exists());
+	}
+
 	/**
 	 * Tests getThreshold and setThreshold.
 	 */
diff --git a/src/test/cpp/hierarchythresholdtestcase.cpp b/src/test/cpp/hierarchythresholdtestcase.cpp
index 65383cd8..1f261c7d 100644
--- a/src/test/cpp/hierarchythresholdtestcase.cpp
+++ b/src/test/cpp/hierarchythresholdtestcase.cpp
@@ -62,7 +62,7 @@ public:
 		auto status = PropertyConfigurator::configure(LOG4CXX_FILE("input/hierarchyThreshold1.properties"));
 		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
 		common();
-		LOGUNIT_ASSERT(Compare::compare(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.1")));
+		LOGUNIT_ASSERT(compareAllowingMissingEmpty(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.1")));
 	}
 
 	void test2()
@@ -70,7 +70,7 @@ public:
 		auto status = PropertyConfigurator::configure(LOG4CXX_FILE("input/hierarchyThreshold2.properties"));
 		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
 		common();
-		LOGUNIT_ASSERT(Compare::compare(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.2")));
+		LOGUNIT_ASSERT(compareAllowingMissingEmpty(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.2")));
 	}
 
 	void test3()
@@ -78,7 +78,7 @@ public:
 		auto status = PropertyConfigurator::configure(LOG4CXX_FILE("input/hierarchyThreshold3.properties"));
 		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
 		common();
-		LOGUNIT_ASSERT(Compare::compare(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.3")));
+		LOGUNIT_ASSERT(compareAllowingMissingEmpty(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.3")));
 	}
 
 	void test4()
@@ -86,7 +86,7 @@ public:
 		auto status = PropertyConfigurator::configure(LOG4CXX_FILE("input/hierarchyThreshold4.properties"));
 		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
 		common();
-		LOGUNIT_ASSERT(Compare::compare(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.4")));
+		LOGUNIT_ASSERT(compareAllowingMissingEmpty(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.4")));
 	}
 
 	void test5()
@@ -94,7 +94,7 @@ public:
 		auto status = PropertyConfigurator::configure(LOG4CXX_FILE("input/hierarchyThreshold5.properties"));
 		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
 		common();
-		LOGUNIT_ASSERT(Compare::compare(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.5")));
+		LOGUNIT_ASSERT(compareAllowingMissingEmpty(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.5")));
 	}
 
 	void test6()
@@ -102,7 +102,7 @@ public:
 		auto status = PropertyConfigurator::configure(LOG4CXX_FILE("input/hierarchyThreshold6.properties"));
 		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
 		common();
-		LOGUNIT_ASSERT(Compare::compare(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.6")));
+		LOGUNIT_ASSERT(compareAllowingMissingEmpty(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.6")));
 	}
 
 	void test7()
@@ -110,7 +110,7 @@ public:
 		auto status = PropertyConfigurator::configure(LOG4CXX_FILE("input/hierarchyThreshold7.properties"));
 		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
 		common();
-		LOGUNIT_ASSERT(Compare::compare(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.7")));
+		LOGUNIT_ASSERT(compareAllowingMissingEmpty(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.7")));
 	}
 
 	void test8()
@@ -118,7 +118,18 @@ public:
 		auto status = PropertyConfigurator::configure(LOG4CXX_FILE("input/hierarchyThreshold8.properties"));
 		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
 		common();
-		LOGUNIT_ASSERT(Compare::compare(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.8")));
+		LOGUNIT_ASSERT(compareAllowingMissingEmpty(TEMP, LOG4CXX_FILE("witness/hierarchyThreshold.8")));
+	}
+
+	// File creation is lazy, so a threshold that filters out every message leaves no file at all.
+	static bool compareAllowingMissingEmpty(const File& actual, const File& witness)
+	{
+		if (!actual.exists())
+		{
+			return witness.length() == 0;
+		}
+
+		return Compare::compare(actual, witness);
 	}
 
 	static void common()
diff --git a/src/test/cpp/rolling/timebasedrollingtest.cpp b/src/test/cpp/rolling/timebasedrollingtest.cpp
index 6c80a0bd..eb5a6d8f 100644
--- a/src/test/cpp/rolling/timebasedrollingtest.cpp
+++ b/src/test/cpp/rolling/timebasedrollingtest.cpp
@@ -261,16 +261,18 @@ private:
 	 * @param[in]     prefix
 	 * @param[in]     fnames
 	 * @param[in]     srcLine
+	 * @param[in]	  startingAt
 	 */
 	template<size_t N>
 	void compareWitnesses
-		( Pool&            pool
+	    ( Pool&            pool
 		, const LogString& prefix
 		, const LogString (&fnames)[N]
 		, int              srcLine
-		)
+		, int		       startingAt = 0
+	)
 	{
-		for (size_t i = 0; i < N; ++i)
+		for (int i = startingAt; i < int(N); ++i)
 		{
 			this->compareWitness(pool, prefix, fnames[i], int(i), srcLine);
 		}
@@ -291,6 +293,7 @@ private:
 	 * @param[in]		fnames
 	 * @param[in]		witnessIdx
 	 * @param[in]		srcLine
+	 * @param[in]		startingAt
 	 */
 	template<size_t N>
 	void checkFilesExist
@@ -299,15 +302,19 @@ private:
 		, const LogString  (&fnames)[N]
 		, int              witnessIdx
 		, int              srcLine
+		, int		       startingAt = 0
 		)
 	{
-		for (size_t i = 0; i < N - 1; ++i)
+		for (int i = startingAt; i < int(N) - 1; ++i)
 		{
 			//std::wcerr << L"Check: " << fnames[i] << L"\n";
 			LOGUNIT_ASSERT_EQUAL_SRCL(true, File(fnames[i]).exists(), srcLine);
 		}
 
-		this->compareWitness(pool, prefix, fnames[witnessIdx], witnessIdx, srcLine);
+		if (witnessIdx >= startingAt)
+		{
+			this->compareWitness(pool, prefix, fnames[witnessIdx], witnessIdx, srcLine);
+		}
 	}
 
 	/**
@@ -420,7 +427,9 @@ public:
 		this->buildTsFnames<4>(pool, LOG4CXX_STR("test1-"), fnames);
 		this->delayUntilNextSecondWithMsg();
 		this->logMsgAndSleep(	pool, nrOfFnames + 1, __LOG4CXX_FUNC__, __LINE__);
-		this->compareWitnesses<4>(	pool, LOG4CXX_STR("test1."), fnames, __LINE__);
+		// Nothing was logged to fnames[0] and since file creation
+		// is lazy the file doesn't exist.
+		this->compareWitnesses<4>(	pool, LOG4CXX_STR("test1."), fnames, __LINE__, 1);
 	}
 
 
@@ -465,7 +474,9 @@ public:
 		logger->addAppender(rfa2);
 
 		this->logMsgAndSleep(	pool, 2, __LOG4CXX_FUNC__, __LINE__, 3);
-		this->compareWitnesses<4>(	pool, LOG4CXX_STR("test2."), fnames, __LINE__);
+		// Nothing was logged to fnames[0] and since file creation
+		// is lazy the file doesn't exist.
+		this->compareWitnesses<4>(	pool, LOG4CXX_STR("test2."), fnames, __LINE__, 1);
 	}
 
 
@@ -494,7 +505,9 @@ public:
 		fnames[nrOfFnames - 1].resize(fnames[nrOfFnames - 1].size() - 3);
 		this->delayUntilNextSecondWithMsg();
 		this->logMsgAndSleep(	pool, nrOfFnames + 1, __LOG4CXX_FUNC__, __LINE__);
-		this->checkFilesExist<4>(	pool, LOG4CXX_STR("test3."), fnames, nrOfFnames - 1, __LINE__);
+		// Nothing was logged to fnames[0] and since file creation
+		// is lazy the file doesn't exist.
+		this->checkFilesExist<4>(	pool, LOG4CXX_STR("test3."), fnames, nrOfFnames - 1, __LINE__, 1);
 	}
 
 	/**
@@ -583,7 +596,9 @@ public:
 
 		this->delayUntilNextSecondWithMsg();
 		this->logMsgAndSleep(	pool, nrOfLogMsgs, __LOG4CXX_FUNC__, __LINE__);
-		this->compareWitnesses<4>(	pool, LOG4CXX_STR("test5."), fnames, __LINE__);
+		// Nothing was logged to fnames[0] and since file creation
+		// is lazy the file doesn't exist.
+		this->compareWitnesses<4>(	pool, LOG4CXX_STR("test5."), fnames, __LINE__, 1);
 	}
 
 	/**