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 9a59a4cb Ensure the AsyncAppender can fallback the appender on an 
exception (#607)
9a59a4cb is described below

commit 9a59a4cbef39090a0743f129d1f79c9b71745536
Author: Stephen Webb <[email protected]>
AuthorDate: Sat Mar 14 12:31:35 2026 +1100

    Ensure the AsyncAppender can fallback the appender on an exception (#607)
    
    * The logging event that caused the exception is sent to the backup 
appender (as indicated in the documentation)
---
 .github/workflows/log4cxx-macos.yml                |   1 +
 .github/workflows/log4cxx-msys2.yml                |   2 +-
 .github/workflows/log4cxx-ubuntu.yml               |   1 +
 .github/workflows/log4cxx-windows-static.yml       |  10 +-
 .github/workflows/log4cxx-windows.yml              |   1 +
 src/main/cpp/CMakeLists.txt                        |  14 ++-
 src/main/cpp/appenderskeleton.cpp                  |   7 ++
 src/main/cpp/asyncappender.cpp                     | 126 ++++++++++++++-------
 src/main/cpp/fallbackerrorhandler.cpp              |  35 ++++--
 src/main/cpp/writerappender.cpp                    |   4 +
 src/main/include/log4cxx/asyncappender.h           |   5 -
 .../log4cxx/private/appenderskeleton_priv.h        |   4 +
 .../include/log4cxx/varia/fallbackerrorhandler.h   |   2 +-
 src/test/cpp/CMakeLists.txt                        |   3 +
 src/test/cpp/asyncappendertestcase.cpp             |  86 +++++++++++++-
 src/test/resources/input/xml/asyncWithFallback.xml |  25 ++++
 16 files changed, 262 insertions(+), 64 deletions(-)

diff --git a/.github/workflows/log4cxx-macos.yml 
b/.github/workflows/log4cxx-macos.yml
index e418a6fc..4a413637 100644
--- a/.github/workflows/log4cxx-macos.yml
+++ b/.github/workflows/log4cxx-macos.yml
@@ -69,6 +69,7 @@ jobs:
         cd build
         cmake \
           -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \
+          -DLOG4CXX_TEST_ONLY_BUILD=1 \
           -DLOG4CXX_ENABLE_ODBC=${{ matrix.odbc }} \
           -DLOG4CXX_MULTIPROCESS_ROLLING_FILE_APPENDER=${{ matrix.multiprocess 
}} \
           -DLOG4CXX_CFSTRING=ON \
diff --git a/.github/workflows/log4cxx-msys2.yml 
b/.github/workflows/log4cxx-msys2.yml
index 26deced4..c9e0112b 100644
--- a/.github/workflows/log4cxx-msys2.yml
+++ b/.github/workflows/log4cxx-msys2.yml
@@ -69,7 +69,7 @@ jobs:
     - name: 'configure and build'
       shell: msys2 {0}
       run: |
-        cmake -G Ninja -S log4cxx -B log4cxx/build -DCMAKE_CXX_COMPILER=${{ 
matrix.cxx }} -DCMAKE_BUILD_TYPE=Debug
+        cmake -G Ninja -S log4cxx -B log4cxx/build -DLOG4CXX_TEST_ONLY_BUILD=1 
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DCMAKE_BUILD_TYPE=Debug
         cmake --build log4cxx/build
 
     - name: 'run unit tests'
diff --git a/.github/workflows/log4cxx-ubuntu.yml 
b/.github/workflows/log4cxx-ubuntu.yml
index e614ff8f..71899d10 100644
--- a/.github/workflows/log4cxx-ubuntu.yml
+++ b/.github/workflows/log4cxx-ubuntu.yml
@@ -109,6 +109,7 @@ jobs:
         set -x
         cmake \
           -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \
+          -DLOG4CXX_TEST_ONLY_BUILD=1 \
           -DLOG4CXX_ENABLE_ODBC=${{ matrix.odbc }} \
           -DLOG4CXX_QT_SUPPORT=${{ matrix.qt }} \
           -DENABLE_MULTITHREAD_TEST=${{ matrix.multithread }} \
diff --git a/.github/workflows/log4cxx-windows-static.yml 
b/.github/workflows/log4cxx-windows-static.yml
index 124b57cd..fc7c4af6 100644
--- a/.github/workflows/log4cxx-windows-static.yml
+++ b/.github/workflows/log4cxx-windows-static.yml
@@ -75,7 +75,15 @@ jobs:
         cd main
         mkdir build
         cd build
-        cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static 
-DBUILD_SHARED_LIBS=off -DLOG4CXX_MULTIPROCESS_ROLLING_FILE_APPENDER=on 
-DLOG4CXX_TEST_PROGRAM_PATH=C:\msys64\usr\bin 
"-DCMAKE_TOOLCHAIN_FILE=$THISDIR/vcpkg/scripts/buildsystems/vcpkg.cmake" ..
+        cmake `
+            -DLOG4CXX_TEST_ONLY_BUILD=1 `
+            -DVCPKG_TARGET_TRIPLET=x64-windows-static `
+            -DBUILD_SHARED_LIBS=off `
+            -DLOG4CXX_MULTIPROCESS_ROLLING_FILE_APPENDER=on `
+            -DLOG4CXX_TEST_PROGRAM_PATH=C:\msys64\usr\bin `
+            
"-DCMAKE_TOOLCHAIN_FILE=$THISDIR/vcpkg/scripts/buildsystems/vcpkg.cmake" `
+            ..
+
         cmake --build .
 
     - name: run unit tests
diff --git a/.github/workflows/log4cxx-windows.yml 
b/.github/workflows/log4cxx-windows.yml
index 67dc667a..a93dd718 100644
--- a/.github/workflows/log4cxx-windows.yml
+++ b/.github/workflows/log4cxx-windows.yml
@@ -77,6 +77,7 @@ jobs:
         $ROOT=Get-Location
         Set-PSDebug -Trace 1
         cmake `
+           -DLOG4CXX_TEST_ONLY_BUILD=1 `
            -DLOG4CXX_CHAR=wchar_t `
            -DLOG4CXX_ENABLE_ODBC=on `
            "-DLOG4CXX_QT_SUPPORT=${{ matrix.qt }}" `
diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt
index 8a2526f4..25edd863 100644
--- a/src/main/cpp/CMakeLists.txt
+++ b/src/main/cpp/CMakeLists.txt
@@ -20,15 +20,25 @@ add_library(log4cxx)
 if(${log4cxx_ABI_VER} GREATER 15)
   set_target_properties(log4cxx PROPERTIES CXX_VISIBILITY_PRESET hidden)
 endif()
+set(PRIVATE_COMPILE_DEFINITIONS)
+set(PUBLIC_COMPILE_DEFINITIONS)
 if(BUILD_SHARED_LIBS)
-    target_compile_definitions(log4cxx PRIVATE LOG4CXX)
+    list(APPEND PRIVATE_COMPILE_DEFINITIONS LOG4CXX)
     if(UNIX AND NOT APPLE)
         # Make defined symbols non-preemptible, which can optimize relocation 
processing
         target_link_options(log4cxx PRIVATE "LINKER:-Bsymbolic")
     endif()
 else()
-    target_compile_definitions(log4cxx PUBLIC LOG4CXX_STATIC)
+    list(APPEND PUBLIC_COMPILE_DEFINITIONS LOG4CXX_STATIC)
 endif()
+if(LOG4CXX_TEST_ONLY_BUILD)
+    list(APPEND PRIVATE_COMPILE_DEFINITIONS 
"ENABLE_FAILING_APPENDER_SIMULATION_TESTING=1")
+endif()
+target_compile_definitions(log4cxx
+    PUBLIC ${PUBLIC_COMPILE_DEFINITIONS}
+    PRIVATE ${PRIVATE_COMPILE_DEFINITIONS}
+)
+
 add_dependencies(log4cxx log4cxx-include)
 
 set(extra_classes "")
diff --git a/src/main/cpp/appenderskeleton.cpp 
b/src/main/cpp/appenderskeleton.cpp
index 12c083fd..e7deed4f 100644
--- a/src/main/cpp/appenderskeleton.cpp
+++ b/src/main/cpp/appenderskeleton.cpp
@@ -206,6 +206,13 @@ void AppenderSkeleton::setOption(const LogString& option,
        {
                setName(value);
        }
+#if ENABLE_FAILING_APPENDER_SIMULATION_TESTING
+       else if (StringHelper::equalsIgnoreCase(option,
+                       LOG4CXX_STR("FAILONMESSAGE"), 
LOG4CXX_STR("failonmessage")))
+       {
+               m_priv->exceptionTriggeringMessage = value;
+       }
+#endif
 }
 
 const spi::ErrorHandlerPtr AppenderSkeleton::getErrorHandler() const
diff --git a/src/main/cpp/asyncappender.cpp b/src/main/cpp/asyncappender.cpp
index 61715c8f..3ba0dd01 100644
--- a/src/main/cpp/asyncappender.cpp
+++ b/src/main/cpp/asyncappender.cpp
@@ -195,6 +195,47 @@ struct AsyncAppender::AsyncAppenderPriv : public 
AppenderSkeleton::AppenderSkele
         */
        std::thread dispatcher;
 
+       /**
+        * Used to determine when to restart dispatch thread.
+       */
+       bool dispatcherActive{ false };
+
+       /**
+        * Used to determine whether to restart dispatch thread.
+       */
+       int dispatcherStartCount{ 0 };
+
+       /**
+        *  The function the dispatcher executes.
+        */
+       void dispatch(const LogString& appenderName);
+
+       /**
+        * Start dispatcher if not already running.
+        */
+       void checkDispatcher(const LogString& appenderName)
+       {
+               // Restart dispatcher if it has stopped by an exception in an 
attached appender.
+               if (!this->dispatcherActive && this->dispatcher.joinable())
+                       this->dispatcher.join();
+
+               if (!this->dispatcher.joinable() && this->dispatcherStartCount 
<= 1)
+               {
+                       std::lock_guard<std::recursive_mutex> lock(this->mutex);
+                       if (!this->dispatcher.joinable())
+                       {
+                               ++this->dispatcherStartCount;
+                               this->dispatcherActive = true;
+                               this->dispatcher = 
ThreadUtility::instance()->createThread
+                                       ( LOG4CXX_STR("AsyncAppender")
+                                       , 
&AsyncAppender::AsyncAppenderPriv::dispatch
+                                       , this
+                                       , appenderName
+                                       );
+                       }
+               }
+       }
+
        void stopDispatcher()
        {
                bufferNotEmpty.notify_all();
@@ -315,12 +356,8 @@ void AsyncAppender::append(const spi::LoggingEventPtr& 
event, Pool& p)
        // Get a copy of this thread's diagnostic context
        event->LoadDC();
 
-       if (!priv->dispatcher.joinable())
-       {
-               std::lock_guard<std::recursive_mutex> lock(priv->mutex);
-               if (!priv->dispatcher.joinable())
-                       priv->dispatcher = 
ThreadUtility::instance()->createThread( LOG4CXX_STR("AsyncAppender"), 
&AsyncAppender::dispatch, this );
-       }
+       priv->checkDispatcher(getName());
+
        if (priv->dispatcher.get_id() == std::this_thread::get_id()) // From an 
appender attached to this?
        {
                std::unique_lock<std::mutex> lock(priv->bufferMutex);
@@ -373,6 +410,7 @@ void AsyncAppender::append(const spi::LoggingEventPtr& 
event, Pool& p)
                        ++priv->blockedCount;
                        priv->bufferNotFull.wait(lock, [this]()
                        {
+                               priv->checkDispatcher(getName());
                                return priv->eventCount - priv->dispatchedCount 
< priv->bufferSize;
                        });
                        --priv->blockedCount;
@@ -574,95 +612,95 @@ DiscardSummary::createEvent(::LOG4CXX_NS::helpers::Pool& 
p,
 }
 #endif
 
-
-void AsyncAppender::dispatch()
+void AsyncAppender::AsyncAppenderPriv::dispatch(const LogString& appenderName)
 {
        size_t discardCount = 0;
        size_t iterationCount = 0;
        size_t waitCount = 0;
-       size_t blockedCount = 0;
-       std::vector<size_t> pendingCountHistogram(priv->bufferSize, 0);
+       size_t producerBlockedCount = 0;
+       int failureCount = 0;
+       std::vector<size_t> pendingCountHistogram(this->bufferSize, 0);
        bool isActive = true;
 
        while (isActive)
        {
                Pool p;
                LoggingEventList events;
-               events.reserve(priv->bufferSize);
-               for (int count = 0; count < 2 && priv->dispatchedCount == 
priv->commitCount; ++count)
+               events.reserve(this->bufferSize);
+               for (int count = 0; count < 2 && this->dispatchedCount == 
this->commitCount; ++count)
                        std::this_thread::yield(); // Wait a bit
-               if (priv->dispatchedCount == priv->commitCount)
+               if (this->dispatchedCount == this->commitCount)
                {
                        ++waitCount;
-                       std::unique_lock<std::mutex> lock(priv->bufferMutex);
-                       priv->bufferNotEmpty.wait(lock, [this]() -> bool
-                               { return 0 < priv->blockedCount || 
priv->dispatchedCount != priv->commitCount || priv->closed; }
+                       std::unique_lock<std::mutex> lock(this->bufferMutex);
+                       this->bufferNotEmpty.wait(lock, [this]() -> bool
+                               { return 0 < this->blockedCount || 
this->dispatchedCount != this->commitCount || this->closed; }
                        );
                }
-               isActive = !priv->isClosed();
+               isActive = !this->isClosed();
 
-               while (events.size() < priv->bufferSize && 
priv->dispatchedCount != priv->commitCount)
+               while (events.size() < this->bufferSize && 
this->dispatchedCount != this->commitCount)
                {
-                       auto index = priv->dispatchedCount % 
priv->buffer.size();
-                       const auto& data = priv->buffer[index];
+                       auto index = this->dispatchedCount % 
this->buffer.size();
+                       const auto& data = this->buffer[index];
                        events.push_back(data.event);
                        if (data.pendingCount < pendingCountHistogram.size())
                                ++pendingCountHistogram[data.pendingCount];
-                       ++priv->dispatchedCount;
+                       ++this->dispatchedCount;
                }
-               priv->bufferNotFull.notify_all();
+               this->bufferNotFull.notify_all();
                {
-                       std::lock_guard<std::mutex> lock(priv->bufferMutex);
-                       blockedCount += priv->blockedCount;
-                       for (auto& discardItem : priv->discardMap)
+                       std::lock_guard<std::mutex> lock(this->bufferMutex);
+                       producerBlockedCount += this->blockedCount;
+                       for (auto& discardItem : this->discardMap)
                        {
                                
events.push_back(discardItem.second.createEvent(p));
                                discardCount += discardItem.second.getCount();
                        }
-                       priv->discardMap.clear();
+                       this->discardMap.clear();
                }
 
                for (auto item : events)
                {
                        try
                        {
-                               priv->appenders.appendLoopOnAppenders(item, p);
+                               this->appenders.appendLoopOnAppenders(item, p);
                        }
                        catch (std::exception& ex)
                        {
-                               if (!priv->isClosed())
-                               {
-                                       
priv->errorHandler->error(LOG4CXX_STR("async dispatcher"), ex, 0, item);
-                                       isActive = false;
-                               }
+                               if (1 < ++failureCount)
+                                       break;
+                               if (!this->isClosed())
+                                       
this->errorHandler->error(LOG4CXX_STR("[") + appenderName + LOG4CXX_STR("] 
AsyncAppender"), ex, spi::ErrorCode::WRITE_FAILURE, item);
                        }
                        catch (...)
                        {
-                               if (!priv->isClosed())
-                               {
-                                       
priv->errorHandler->error(LOG4CXX_STR("async dispatcher"));
-                                       isActive = false;
-                               }
+                               if (1 < ++failureCount)
+                                       break;
+                               if (!this->isClosed())
+                                       
this->errorHandler->error(LOG4CXX_STR("[") + appenderName + LOG4CXX_STR("] 
AsyncAppender unknown exception thrown"));
                        }
                }
                ++iterationCount;
+               if (1 < failureCount)
+                       break;
        }
        if (LogLog::isDebugEnabled())
        {
                Pool p;
-               LogString msg(LOG4CXX_STR("[") + getName() + LOG4CXX_STR("] 
AsyncAppender"));
+               LogString msg(LOG4CXX_STR("[") + appenderName + LOG4CXX_STR("] 
AsyncAppender"));
 #ifdef _DEBUG
                msg += LOG4CXX_STR(" iterationCount ");
                StringHelper::toString(iterationCount, p, msg);
                msg += LOG4CXX_STR(" waitCount ");
                StringHelper::toString(waitCount, p, msg);
-               msg += LOG4CXX_STR(" blockedCount ");
-               StringHelper::toString(blockedCount, p, msg);
+               msg += LOG4CXX_STR(" producerBlockedCount ");
+               StringHelper::toString(producerBlockedCount, p, msg);
                msg += LOG4CXX_STR(" commitCount ");
-               StringHelper::toString(priv->commitCount, p, msg);
+               StringHelper::toString(this->commitCount, p, msg);
 #endif
                msg += LOG4CXX_STR(" dispatchedCount ");
-               StringHelper::toString(priv->dispatchedCount, p, msg);
+               StringHelper::toString(this->dispatchedCount, p, msg);
                msg += LOG4CXX_STR(" discardCount ");
                StringHelper::toString(discardCount, p, msg);
                msg += LOG4CXX_STR(" pendingCountHistogram");
@@ -673,5 +711,7 @@ void AsyncAppender::dispatch()
                }
                LogLog::debug(msg);
        }
-
+       this->dispatcherActive = false;
+       if (0 < this->blockedCount) // Restart this dispatcher?
+               this->bufferNotFull.notify_all();
 }
diff --git a/src/main/cpp/fallbackerrorhandler.cpp 
b/src/main/cpp/fallbackerrorhandler.cpp
index 9e364faf..e526e36b 100644
--- a/src/main/cpp/fallbackerrorhandler.cpp
+++ b/src/main/cpp/fallbackerrorhandler.cpp
@@ -67,19 +67,36 @@ void FallbackErrorHandler::setLogger(const LoggerPtr& 
logger)
        m_priv->appenderHolders.emplace(logger->getName(), logger);
 }
 
-void FallbackErrorHandler::error(const LogString& message,
-       const std::exception& e,
-       int errorCode) const
+void FallbackErrorHandler::error(const LogString& message) const
 {
-       error(message, e, errorCode, 0);
+       LogLog::warn(message);
+       m_priv->errorReported = true;
+}
+
+void FallbackErrorHandler::error
+       ( const LogString& message
+       , const std::exception& ex
+       , int errorCode
+       ) const
+{
+       error(message, ex, errorCode, 0);
 }
 
-void FallbackErrorHandler::error(const LogString& message,
-       const std::exception& e,
-       int, const spi::LoggingEventPtr&) const
+void FallbackErrorHandler::error
+       ( const LogString& message
+       , const std::exception& ex
+       , int errorCode
+       , const spi::LoggingEventPtr& event
+       ) const
 {
+       Pool p;
        if (LogLog::isDebugEnabled())
-               LogLog::debug(LOG4CXX_STR("FB: The following error reported: ") 
+ message, e);
+       {
+               LogString msg{ LOG4CXX_STR("FB: error code ") };
+               StringHelper::toString(errorCode, p, msg);
+               LogLog::debug(msg);
+       }
+       LogLog::warn(message, ex);
 
        AppenderPtr primaryLocked = m_priv->primary.lock();
        AppenderPtr backupLocked = m_priv->backup.lock();
@@ -119,6 +136,8 @@ void FallbackErrorHandler::error(const LogString& message,
                }
        }
        m_priv->errorReported = true;
+       if (event)
+               backupLocked->doAppend(event, p);
 }
 
 void FallbackErrorHandler::setAppender(const AppenderPtr& primary1)
diff --git a/src/main/cpp/writerappender.cpp b/src/main/cpp/writerappender.cpp
index 2f9a8fce..3606c0e9 100644
--- a/src/main/cpp/writerappender.cpp
+++ b/src/main/cpp/writerappender.cpp
@@ -226,6 +226,10 @@ void WriterAppender::setEncoding(const LogString& enc)
 
 void WriterAppender::subAppend(const spi::LoggingEventPtr& event, Pool& p)
 {
+#if ENABLE_FAILING_APPENDER_SIMULATION_TESTING
+       if (event->getRenderedMessage() == _priv->exceptionTriggeringMessage)
+               throw RuntimeException(LOG4CXX_STR("Simulated fault"));
+#endif
        LogString msg;
        _priv->layout->format(msg, event, p);
 
diff --git a/src/main/include/log4cxx/asyncappender.h 
b/src/main/include/log4cxx/asyncappender.h
index 6e3212fe..fe9ff01b 100644
--- a/src/main/include/log4cxx/asyncappender.h
+++ b/src/main/include/log4cxx/asyncappender.h
@@ -251,11 +251,6 @@ class LOG4CXX_EXPORT AsyncAppender :
                AsyncAppender(const AsyncAppender&);
                AsyncAppender& operator=(const AsyncAppender&);
 
-               /**
-                *  Dispatch routine.
-                */
-               void dispatch();
-
 }; // class AsyncAppender
 LOG4CXX_PTR_DEF(AsyncAppender);
 }  //  namespace log4cxx
diff --git a/src/main/include/log4cxx/private/appenderskeleton_priv.h 
b/src/main/include/log4cxx/private/appenderskeleton_priv.h
index 6c512b42..8afaed7e 100644
--- a/src/main/include/log4cxx/private/appenderskeleton_priv.h
+++ b/src/main/include/log4cxx/private/appenderskeleton_priv.h
@@ -81,6 +81,10 @@ struct AppenderSkeleton::AppenderSkeletonPrivate
        @returns true if the appender was not already closed
        */
        bool setClosed();
+
+#if ENABLE_FAILING_APPENDER_SIMULATION_TESTING
+       LogString exceptionTriggeringMessage;
+#endif
 };
 
 }
diff --git a/src/main/include/log4cxx/varia/fallbackerrorhandler.h 
b/src/main/include/log4cxx/varia/fallbackerrorhandler.h
index 5c218831..3fcf9f0d 100644
--- a/src/main/include/log4cxx/varia/fallbackerrorhandler.h
+++ b/src/main/include/log4cxx/varia/fallbackerrorhandler.h
@@ -99,7 +99,7 @@ class LOG4CXX_EXPORT FallbackErrorHandler :
                Print a the error message passed as parameter on
                <code>System.err</code>.
                */
-               void error(const LogString& /* message */) const override {}
+               void error(const LogString& message) const override;
 
                /**
                The appender to which this error handler is attached.
diff --git a/src/test/cpp/CMakeLists.txt b/src/test/cpp/CMakeLists.txt
index 33d132f4..ee114340 100644
--- a/src/test/cpp/CMakeLists.txt
+++ b/src/test/cpp/CMakeLists.txt
@@ -121,6 +121,9 @@ elseif(CMAKE_BUILD_TYPE)
 else()
   set(TEST_COMPILE_DEFINITIONS _DEBUG)
 endif()
+if(LOG4CXX_TEST_ONLY_BUILD)
+  list(APPEND TEST_COMPILE_DEFINITIONS 
"ENABLE_FAILING_APPENDER_SIMULATION_TESTING=1")
+endif()
 
 get_filename_component(UNIT_TEST_WORKING_DIR ../resources ABSOLUTE)
 if(LOG4CXX_CFSTRING)
diff --git a/src/test/cpp/asyncappendertestcase.cpp 
b/src/test/cpp/asyncappendertestcase.cpp
index 276d6705..1d562dac 100644
--- a/src/test/cpp/asyncappendertestcase.cpp
+++ b/src/test/cpp/asyncappendertestcase.cpp
@@ -34,9 +34,11 @@
 #include <log4cxx/spi/location/locationinfo.h>
 #include <log4cxx/xml/domconfigurator.h>
 #include <log4cxx/propertyconfigurator.h>
+#include <log4cxx/fileappender.h>
 #include <log4cxx/file.h>
 #include <ostream>
 #include <thread>
+#include <fstream>
 
 #if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
 #include <fmt/core.h>
@@ -166,6 +168,9 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                LOGUNIT_TEST(testXMLConfiguration);
                LOGUNIT_TEST(testAsyncLoggerXML);
                LOGUNIT_TEST(testRecursiveConfiguration);
+#endif
+#if ENABLE_FAILING_APPENDER_SIMULATION_TESTING
+               LOGUNIT_TEST(testAsyncAppenderFallback);
 #endif
                LOGUNIT_TEST(testAsyncLoggerProperties);
 #if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
@@ -308,11 +313,13 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                        const std::vector<spi::LoggingEventPtr>& v = 
vectorAppender->getVector();
                        LOGUNIT_ASSERT_EQUAL(LEN, v.size());
                        Pool p;
-                       for (size_t i = 0; i < LEN; i++)
+                       int i{ 0 };
+                       for (auto e : v)
                        {
                                LogString m(LOG4CXX_STR("message"));
                                StringHelper::toString(i, p, m);
-                               LOGUNIT_ASSERT(v[i]->getRenderedMessage() == m);
+                               LOGUNIT_ASSERT_EQUAL(m, 
e->getRenderedMessage());
+                               ++i;
                        }
                        LOGUNIT_ASSERT_EQUAL(true, vectorAppender->isClosed());
                }
@@ -657,8 +664,11 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                                if (auto async2 = 
LOG4CXX_NS::cast<AsyncAppender>(attachedAppender))
                                {
                                        for (auto appender : 
async2->getAllAppenders())
-                                               if (vectorAppender = 
LOG4CXX_NS::cast<VectorAppender>(appender))
+                                       {
+                                               vectorAppender = 
LOG4CXX_NS::cast<VectorAppender>(appender);
+                                               if (vectorAppender)
                                                        break;
+                                       }
                                }
                        }
                        LOGUNIT_ASSERT(vectorAppender);
@@ -679,6 +689,76 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                }
 #endif
 
+#if ENABLE_FAILING_APPENDER_SIMULATION_TESTING
+               void testAsyncAppenderFallback()
+               {
+                       // Configure Log4cxx
+                       auto status = 
xml::DOMConfigurator::configure("input/xml/asyncWithFallback.xml");
+                       LOGUNIT_ASSERT_EQUAL(status, 
spi::ConfigurationStatus::Configured);
+
+                       // Check configuration is as expected
+                       auto  root = Logger::getRootLogger();
+                       auto appenders = root->getAllAppenders();
+                       LOGUNIT_ASSERT_EQUAL(1, int(appenders.size()));
+                       auto asyncAppender = 
LOG4CXX_NS::cast<AsyncAppender>(appenders.front());
+                       LOGUNIT_ASSERT(asyncAppender);
+                       auto asyncAppenders = asyncAppender->getAllAppenders();
+                       LOGUNIT_ASSERT_EQUAL(1, int(asyncAppenders.size()));
+                       auto primaryAppender = 
LOG4CXX_NS::cast<FileAppender>(asyncAppenders.front());
+                       LOGUNIT_ASSERT(primaryAppender);
+                       auto primaryFileName = primaryAppender->getFile();
+                       LOGUNIT_ASSERT(11 < primaryFileName.size());
+                       LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("Primary.log"), 
primaryFileName.substr(primaryFileName.size() - 11));
+
+                       // Log some messages
+                       size_t LEN = 2000;
+                       for (size_t i = 0; i < LEN; i++)
+                       {
+                               LOG4CXX_INFO_ASYNC(root, "message " << i);
+                       }
+                       std::this_thread::sleep_for( std::chrono::milliseconds( 
100 ) );
+                       asyncAppenders = asyncAppender->getAllAppenders();
+                       LOGUNIT_ASSERT_EQUAL(1, int(asyncAppenders.size()));
+                       auto backupAppender = 
LOG4CXX_NS::cast<FileAppender>(asyncAppenders.front());
+                       LOGUNIT_ASSERT(backupAppender);
+                       auto backupFileName = backupAppender->getFile();
+                       LOGUNIT_ASSERT(10 < backupFileName.size());
+                       LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("Backup.log"), 
backupFileName.substr(backupFileName.size() - 10));
+
+                       // Check all messages were written
+                       std::vector<int> messageCount;
+                       for (auto const& filePathLS : {primaryFileName, 
backupFileName})
+                       {
+                               LOG4CXX_ENCODE_CHAR(filePath, filePathLS);
+                               std::ifstream input(filePath);
+                               for (std::string line; std::getline(input, 
line);)
+                               {
+                                        auto pos = line.rfind(' ');
+                                        if (line.npos != pos && pos + 1 < 
line.size())
+                                        {
+                                               try
+                                               {
+                                                       auto msgNumber = 
std::stoull(line.substr(pos));
+                                                       if (messageCount.size() 
<= msgNumber)
+                                                               
messageCount.resize(msgNumber + 1);
+                                                       
++messageCount[msgNumber];
+                                               }
+                                               catch (std::exception const& ex)
+                                               {
+                                                       LogString msg;
+                                                       
helpers::Transcoder::decode(ex.what(), msg);
+                                                       msg += LOG4CXX_STR(" 
processing\n");
+                                                       
helpers::Transcoder::decode(line, msg);
+                                                       
helpers::LogLog::warn(msg);
+                                               }
+                                        }
+                               }
+                       }
+                       for (auto& count : messageCount)
+                               LOGUNIT_ASSERT_EQUAL(1, count);
+               }
+#endif
+
                void testAsyncLoggerProperties()
                {
                        // Configure Log4cxx
diff --git a/src/test/resources/input/xml/asyncWithFallback.xml 
b/src/test/resources/input/xml/asyncWithFallback.xml
new file mode 100644
index 00000000..6d139d28
--- /dev/null
+++ b/src/test/resources/input/xml/asyncWithFallback.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/";>
+  <appender name="Primary" class="FileAppender">
+    <param name="File" 
value="${PROGRAM_FILE_PATH.PARENT_PATH}/${PROGRAM_FILE_PATH.STEM}-Primary.log" 
/>
+    <param name="Append" value="false"/>
+    <param name="FailOnMessage" value="message 1000"/>
+    <layout class="PatternLayout">
+      <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
+    </layout>
+  </appender>
+  <appender name="Backup" class="FileAppender">
+    <param name="File" 
value="${PROGRAM_FILE_PATH.PARENT_PATH}/${PROGRAM_FILE_PATH.STEM}-Backup.log" />
+    <param name="Append" value="false"/>
+    <layout class="PatternLayout">
+      <param name="ConversionPattern" value="%d %-5p %c{2} - %m%n"/>
+    </layout>
+  </appender>
+  <appender name="ASYNC" class="AsyncAppender">
+    <appender-ref ref="Primary" fallback-ref="Backup"/>
+  </appender>
+  <root>
+    <priority value ="INFO" />
+    <appender-ref ref="ASYNC" />
+  </root>
+</log4j:configuration>

Reply via email to