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/13 23:45:58 UTC
(logging-log4cxx) branch master updated: Avoid a Logger race condition when re-parenting while logging (#777)
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 4deeeb26 Avoid a Logger race condition when re-parenting while logging (#777)
4deeeb26 is described below
commit 4deeeb26f5731d14f668117474da6f51f314108b
Author: Stephen Webb <st...@ieee.org>
AuthorDate: Wed Oct 14 10:45:52 2026 +1100
Avoid a Logger race condition when re-parenting while logging (#777)
* Add a cache of the active set of appenders to each Logger
* Retain the old cache until the new appenders are installed
* A mutex lock has less overhead than std::atomic_load/std::atomic_store on gcc 9
---
src/main/cpp/logger.cpp | 167 +++++++---
src/main/include/log4cxx/helpers/lazyptr.h | 27 +-
src/test/cpp/CMakeLists.txt | 1 +
src/test/cpp/loggerappendertests.cpp | 492 +++++++++++++++++++++++++++++
4 files changed, 639 insertions(+), 48 deletions(-)
diff --git a/src/main/cpp/logger.cpp b/src/main/cpp/logger.cpp
index 96b615a3..6f3f7b8d 100644
--- a/src/main/cpp/logger.cpp
+++ b/src/main/cpp/logger.cpp
@@ -40,9 +40,6 @@ struct Logger::LoggerPrivate
{
LoggerPrivate(const LogString& name1)
: name(name1)
- , repositoryRaw(0)
- , additive(true)
- , levelData(Level::getData())
{}
/**
@@ -56,10 +53,84 @@ struct Logger::LoggerPrivate
which case it is inherited form the hierarchy. */
LevelPtr level;
+#ifdef __cpp_lib_atomic_shared_ptr
+ /**
+ Holds all the appenders targeted by this logger.
+ The set of appenders is calculated when the first event is added.
+ */
+ std::atomic<AppenderAttachableImplPtr> appenderCache;
+ AppenderAttachableImplPtr getAppenderCache()
+ {
+ auto result = this->appenderCache.load(std::memory_order_acquire);
+ if (!result)
+ {
+ result = std::make_shared<AppenderAttachableImpl>();
+ setAppenderCache(*result);
+ this->appenderCache.store(result, std::memory_order_release);
+ }
+ return result;
+ }
+ void resetAppenderCache()
+ {
+ this->appenderCache.store(nullptr);
+ }
+
/**
The parent of this logger. All loggers have at least one
- ancestor which is the root logger. */
- LoggerPtr parent;
+ ancestor which is the root logger.
+ */
+ std::atomic<LoggerPtr> parent;
+ LoggerPtr getParent() const
+ {
+ return this->parent.load(std::memory_order_acquire);
+ }
+ void setParent(const LoggerPtr& newValue)
+ {
+ this->parent.store(newValue, std::memory_order_release);
+ }
+#else // !defined(__cpp_lib_atomic_shared_ptr)
+ /**
+ Holds all the appenders targeted by this logger.
+ The set of appenders is calculated when the first event is added.
+ */
+ AppenderAttachableImplPtr appenderCache;
+ AppenderAttachableImplPtr getAppenderCache()
+ {
+ std::lock_guard<std::mutex> lock( this->mutex );
+ auto result = this->appenderCache;
+ if (!result)
+ {
+ result = std::make_shared<AppenderAttachableImpl>();
+ setAppenderCache(*result);
+ this->appenderCache = result;
+ }
+ return result;
+ }
+ void resetAppenderCache()
+ {
+ std::lock_guard<std::mutex> lock( this->mutex );
+ this->appenderCache.reset();
+ }
+
+ /**
+ The parent of this logger. All loggers have at least one
+ ancestor which is the root logger.
+ */
+ LoggerPtr parent;
+ mutable std::mutex mutex;
+
+ LoggerPtr getParent() const
+ {
+ std::lock_guard<std::mutex> lock( this->mutex );
+ return this->parent;
+ }
+
+ void setParent(const LoggerPtr& newValue)
+ {
+ std::lock_guard<std::mutex> lock( this->mutex );
+ this->parent = newValue;
+ }
+#endif // !defined(__cpp_lib_atomic_shared_ptr)
/** The resourceBundle for localized messages.
@@ -69,20 +140,36 @@ struct Logger::LoggerPrivate
// Loggers need to know what Hierarchy they are in
- spi::LoggerRepository* repositoryRaw;
+ spi::LoggerRepository* repositoryRaw{ nullptr };
LazyPtr<AppenderAttachableImpl> aai;
- /** Additivity is set to true by default, that is children inherit
- the appenders of their ancestors by default. If this variable is
- set to <code>false</code> then the appenders found in the
+ /** By default children inherit the appenders of their ancestors.
+ If <code>false</code> then the appenders found in the
ancestors of this logger are not used. However, the children
of this logger will inherit its appenders, unless the children
have their additivity flag set to <code>false</code> too. See
the user manual for more details. */
- bool additive;
+ bool additive{ true };
+
+ Level::DataPtr levelData{ Level::getData() };
+
+ void setAppenderCache(AppenderAttachableImpl& aCache)
+ {
+ auto pPriv = this;
+ do
+ {
+ if (auto p = pPriv->aai.get_ptr())
+ aCache.addAppendersOf(*p);
+ if (!pPriv->additive)
+ break;
+ LoggerPtr pParent = pPriv->parent;
+ if (!pParent)
+ break;
+ pPriv = pParent->m_priv.get();
+ } while (pPriv);
+ }
- Level::DataPtr levelData;
};
IMPLEMENT_LOG4CXX_OBJECT(Logger)
@@ -104,18 +191,26 @@ void Logger::addAppender(const AppenderPtr& newAppender)
if (auto rep = getHierarchy())
{
rep->fireAddAppenderEvent(this, newAppender.get());
+ rep->updateChildren(this);
}
+ m_priv->resetAppenderCache();
}
bool Logger::replaceAppender(const AppenderPtr& oldAppender, const AppenderPtr& newAppender)
{
bool result = false;
if (auto p = m_priv->aai.get_ptr())
+ {
result = p->replaceAppender(oldAppender, newAppender);
+ }
if (result)
{
if (auto rep = getHierarchy())
+ {
rep->fireAddAppenderEvent(this, newAppender.get());
+ rep->updateChildren(this);
+ }
+ m_priv->resetAppenderCache();
}
return result;
}
@@ -128,7 +223,9 @@ void Logger::replaceAppenders( const AppenderList& newList)
{
for (auto const& item : newList)
rep->fireAddAppenderEvent(this, item.get());
+ rep->updateChildren(this);
}
+ m_priv->resetAppenderCache();
}
void Logger::reconfigure( const AppenderList& newList, bool newAdditivity )
@@ -140,23 +237,8 @@ void Logger::reconfigure( const AppenderList& newList, bool newAdditivity )
void Logger::callAppenders(const spi::LoggingEventPtr& event) const
{
- int writes = 0;
-
- for (const Logger* logger = this;
- logger != 0;
- logger = logger->m_priv->parent.get())
- {
- if (auto p = logger->m_priv->aai.get_ptr())
- writes += p->appendLoopOnAppenders(event);
-
- if (!logger->m_priv->additive)
- {
- break;
- }
- }
-
+ int writes = m_priv->getAppenderCache()->appendLoopOnAppenders(event);
auto rep = getHierarchy();
-
if (writes == 0 && rep)
{
rep->emitNoAppenderWarning(this);
@@ -312,7 +394,7 @@ AppenderPtr Logger::getAppender(const LogString& name1) const
const LevelPtr& Logger::getEffectiveLevel() const
{
- for (const Logger* l = this; l != 0; l = l->m_priv->parent.get())
+ for (const Logger* l = this; l != 0; l = l->m_priv->getParent().get())
{
if (l->m_priv->level != 0)
{
@@ -338,7 +420,7 @@ LoggerRepository* Logger::getHierarchy() const
ResourceBundlePtr Logger::getResourceBundle() const
{
- for (const Logger* l = this; l != 0; l = l->m_priv->parent.get())
+ for (const Logger* l = this; l != 0; l = l->m_priv->getParent().get())
{
if (l->m_priv->resourceBundle != 0)
{
@@ -380,7 +462,7 @@ LogString Logger::getResourceBundleString(const LogString& key) const
LoggerPtr Logger::getParent() const
{
- return m_priv->parent;
+ return m_priv->getParent();
}
const LevelPtr& Logger::getLevel() const
@@ -608,13 +690,15 @@ void Logger::removeAllAppenders()
auto currentAppenders = p->getAllAppenders();
p->removeAllAppenders();
- auto rep = getHierarchy();
- if(rep){
+ if (auto rep = getHierarchy())
+ {
for(AppenderPtr appender : currentAppenders){
rep->fireRemoveAppenderEvent(this, appender.get());
}
+ rep->updateChildren(this);
}
}
+ m_priv->resetAppenderCache();
}
bool Logger::removeAppender(const AppenderPtr& appender)
@@ -623,9 +707,14 @@ bool Logger::removeAppender(const AppenderPtr& appender)
if (auto p = m_priv->aai.get_ptr())
{
result = p->removeAppender(appender);
- if (auto rep = getHierarchy())
+ if (result)
{
- rep->fireRemoveAppenderEvent(this, appender.get());
+ if (auto rep = getHierarchy())
+ {
+ rep->fireRemoveAppenderEvent(this, appender.get());
+ rep->updateChildren(this);
+ }
+ m_priv->resetAppenderCache();
}
}
return result;
@@ -651,6 +740,9 @@ void Logger::removeHierarchy()
void Logger::setAdditivity(bool additive1)
{
m_priv->additive = additive1;
+ if (auto rep = getHierarchy())
+ rep->updateChildren(this);
+ m_priv->resetAppenderCache();
}
void Logger::setHierarchy(spi::LoggerRepository* repository1)
@@ -660,9 +752,9 @@ void Logger::setHierarchy(spi::LoggerRepository* repository1)
void Logger::setParent(const LoggerPtr& parentLogger)
{
- m_priv->parent = parentLogger;
+ m_priv->setParent(parentLogger);
updateCachedConfiguration();
- if (auto rep = dynamic_cast<Hierarchy*>(getHierarchy()))
+ if (auto rep = getHierarchy())
rep->updateChildren(this);
}
@@ -675,7 +767,7 @@ void Logger::setLevel(const LevelPtr& level1)
{
m_priv->level = level1;
updateCachedConfiguration();
- if (auto rep = dynamic_cast<Hierarchy*>(getHierarchy()))
+ if (auto rep = getHierarchy())
rep->updateChildren(this);
}
}
@@ -683,6 +775,7 @@ void Logger::setLevel(const LevelPtr& level1)
void Logger::updateCachedConfiguration()
{
m_threshold = getEffectiveLevel()->toInt();
+ m_priv->resetAppenderCache();
}
const LogString& Logger::getName() const
diff --git a/src/main/include/log4cxx/helpers/lazyptr.h b/src/main/include/log4cxx/helpers/lazyptr.h
index 9fc1ad12..a409da8f 100644
--- a/src/main/include/log4cxx/helpers/lazyptr.h
+++ b/src/main/include/log4cxx/helpers/lazyptr.h
@@ -29,15 +29,18 @@ template <typename T>
class LazyPtr
{
private:
+ std::function<void(T*)> m_initialiser;
std::atomic<T*> m_ptr{nullptr};
public: // ...structors
- LazyPtr() = default;
+ LazyPtr(std::function<void(T*)> initialiser = {})
+ : m_initialiser(initialiser)
+ {}
// Clean up allocated resource on destruction
~LazyPtr()
{
- delete m_ptr.load(std::memory_order_relaxed);
+ delete m_ptr.load(std::memory_order_acquire);
}
// Prevent copying to avoid double-free errors
@@ -46,7 +49,7 @@ public: // ...structors
// Allow moving
LazyPtr(LazyPtr&& other) noexcept
- : m_ptr(other.m_ptr.exchange(nullptr, std::memory_order_relaxed))
+ : m_ptr(other.m_ptr.exchange(nullptr, std::memory_order_acq_rel))
{}
public: // Operators
@@ -55,9 +58,9 @@ public: // Operators
{
if (this != &other)
{
- delete m_ptr.load(std::memory_order_relaxed);
- m_ptr.store(other.m_ptr.exchange(nullptr, std::memory_order_relaxed),
- std::memory_order_relaxed);
+ delete m_ptr.load(std::memory_order_acquire);
+ m_ptr.store(other.m_ptr.exchange(nullptr, std::memory_order_acq_rel),
+ std::memory_order_release);
}
return *this;
}
@@ -71,26 +74,28 @@ public: // Operators
// Explicit bool conversion
explicit operator bool() const noexcept
{
- return m_ptr.load(std::memory_order_relaxed) != nullptr;
+ return m_ptr.load(std::memory_order_acquire) != nullptr;
}
// The raw pointer value
T* get_ptr() const noexcept
{
- return m_ptr.load(std::memory_order_relaxed);
+ return m_ptr.load(std::memory_order_acquire);
}
private: // Modifiers
// Lazily initialize if required
T* get_or_throw()
{
- T* p = m_ptr.load(std::memory_order_relaxed);
+ T* p = m_ptr.load(std::memory_order_acquire);
if (!p)
{
auto* new_p = new T();
+ if (m_initialiser)
+ m_initialiser(new_p);
if (!m_ptr.compare_exchange_strong(p, new_p
- , std::memory_order_relaxed
- , std::memory_order_relaxed
+ , std::memory_order_release
+ , std::memory_order_acquire
)
)
{
diff --git a/src/test/cpp/CMakeLists.txt b/src/test/cpp/CMakeLists.txt
index e3d82518..2e24c368 100644
--- a/src/test/cpp/CMakeLists.txt
+++ b/src/test/cpp/CMakeLists.txt
@@ -62,6 +62,7 @@ set(ALL_LOG4CXX_TESTS
leveltestcase
levelchangetestcase
loggertestcase
+ loggerappendertests
loggerthresholdtests
mdctestcase
minimumtestcase
diff --git a/src/test/cpp/loggerappendertests.cpp b/src/test/cpp/loggerappendertests.cpp
new file mode 100644
index 00000000..39212bbb
--- /dev/null
+++ b/src/test/cpp/loggerappendertests.cpp
@@ -0,0 +1,492 @@
+/*
+ * 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.
+ */
+
+#include "logunit.h"
+#include <log4cxx/logger.h>
+#include <log4cxx/hierarchy.h>
+#include <log4cxx/appenderskeleton.h>
+#include <log4cxx/helpers/stringhelper.h>
+#include <log4cxx/helpers/transcoder.h>
+
+using namespace log4cxx;
+
+class CountingAppender : public AppenderSkeleton
+{
+ public:
+ int count;
+
+ CountingAppender(int id) : count(0)
+ {
+ LogString name = LOG4CXX_STR("counter_");
+ helpers::StringHelper::toString(id, name);
+ setName(name);
+ }
+
+ void append( const spi::LoggingEventPtr& event ) override
+ {
+ count++;
+ }
+
+ void close() override
+ {}
+
+ bool requiresLayout() const override
+ {
+ return false;
+ }
+};
+using CountingAppenderPtr = std::shared_ptr<CountingAppender>;
+
+LOGUNIT_CLASS(LoggerAppenderCacheConsistencyTest)
+{
+ LOGUNIT_TEST_SUITE(LoggerAppenderCacheConsistencyTest);
+
+ // Basic hierarchy tests
+ LOGUNIT_TEST(testRootLoggerInitialState);
+ LOGUNIT_TEST(testSingleLoggerReplaceAppender);
+ LOGUNIT_TEST(testSingleLoggerAddsToRoot);
+
+ // Simple parent-child tests
+ LOGUNIT_TEST(testTwoLevelHierarchyChildInherits);
+ LOGUNIT_TEST(testTwoLevelHierarchyChildRemovesAdditivity);
+ LOGUNIT_TEST(testTwoLevelHierarchyParentChanges);
+
+ // Multi-level hierarchy tests
+ LOGUNIT_TEST(testThreeLevelHierarchyAllInherit);
+ LOGUNIT_TEST(testThreeLevelHierarchyMiddleRemovesAdditivity);
+ LOGUNIT_TEST(testThreeLevelHierarchyLeafRemovesAdditivity);
+ LOGUNIT_TEST(testFourLevelHierarchyMiddleRemovesAdditivity);
+
+ // Dynamic parent change tests
+ LOGUNIT_TEST(testSetParentFromNullToValid);
+ LOGUNIT_TEST(testSetParentChangeParent);
+ LOGUNIT_TEST(testSetParentMultipleTimes);
+
+ // Appender change propagation tests
+ LOGUNIT_TEST(testRootAppenderChangePropagatesToChildren);
+
+ LOGUNIT_TEST_SUITE_END();
+
+private:
+ HierarchyPtr hierarchy;
+ LoggerPtr rootLogger;
+ CountingAppenderPtr appender1;
+ CountingAppenderPtr appender2;
+ CountingAppenderPtr appender3;
+
+public:
+ void setUp() override
+ {
+ appender1 = std::make_shared<CountingAppender>(1);
+ hierarchy = Hierarchy::create();
+ rootLogger = hierarchy->getRootLogger();
+ rootLogger->addAppender(appender1);
+ appender2 = std::make_shared<CountingAppender>(2);
+ appender3 = std::make_shared<CountingAppender>(3);
+ }
+
+ void tearDown() override
+ {
+ hierarchy->resetConfiguration();
+ hierarchy.reset();
+ appender1.reset();
+ appender2.reset();
+ }
+
+public:
+ // Test 1: Root logger initial state
+ void testRootLoggerInitialState()
+ {
+ rootLogger->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ }
+
+ // Test 2: Single logger replace appender
+ void testSingleLoggerReplaceAppender()
+ {
+ auto logger = hierarchy->getLogger(LOG4CXX_STR("com.example"));
+ logger->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+
+ rootLogger->replaceAppender(appender1, appender2);
+ logger->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ }
+
+ // Test 3: Single logger adds to root
+ void testSingleLoggerAddsToRoot()
+ {
+ LoggerPtr logger = hierarchy->getLogger(LOG4CXX_STR("com.example"));
+ logger->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+
+ logger->addAppender(appender2);
+ logger->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+
+ logger->removeAppender(appender2);
+ logger->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ }
+
+ // Test 4: Two-level hierarchy - child adds to root
+ void testTwoLevelHierarchyChildInherits()
+ {
+ LoggerPtr parent = hierarchy->getLogger(LOG4CXX_STR("com"));
+ LoggerPtr child = hierarchy->getLogger(LOG4CXX_STR("com.example"));
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+
+ child->addAppender(appender2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+
+ child->removeAllAppenders();
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ }
+
+ // Test 5: Two-level hierarchy - child removes additivity
+ void testTwoLevelHierarchyChildRemovesAdditivity()
+ {
+ LoggerPtr parent = hierarchy->getLogger(LOG4CXX_STR("com"));
+ LoggerPtr child = hierarchy->getLogger(LOG4CXX_STR("com.example"));
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+
+ child->addAppender(appender2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ child->setAdditivity(false);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 2);
+ }
+
+ // Test 6: Two-level hierarchy - parent change after child removes additivity
+ void testTwoLevelHierarchyParentChanges()
+ {
+ LoggerPtr parent = hierarchy->getLogger(LOG4CXX_STR("com"));
+ LoggerPtr child = hierarchy->getLogger(LOG4CXX_STR("com.example"));
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+
+ child->addAppender(appender2);
+ child->setAdditivity(false);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ // Change parent - child should maintain its additivity
+ rootLogger->replaceAppender(appender1, appender3);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 2);
+ LOGUNIT_ASSERT_EQUAL(appender3->count, 0);
+ }
+
+ // Test 7: Three-level hierarchy - all inherit
+ void testThreeLevelHierarchyAllInherit()
+ {
+ LoggerPtr level1 = hierarchy->getLogger(LOG4CXX_STR("com"));
+ LoggerPtr level2 = hierarchy->getLogger(LOG4CXX_STR("com.example"));
+ LoggerPtr level3 = hierarchy->getLogger(LOG4CXX_STR("com.example.app"));
+
+ level1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ level2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ level3->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ // Change parent - additivity should persist
+ rootLogger->replaceAppender(appender1, appender2);
+ level1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ level2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 2);
+ level3->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+ }
+
+ // Test 8: Three-level hierarchy - middle removes additivity
+ void testThreeLevelHierarchyMiddleRemovesAdditivity()
+ {
+ LoggerPtr level1 = hierarchy->getLogger(LOG4CXX_STR("com"));
+ LoggerPtr level2 = hierarchy->getLogger(LOG4CXX_STR("com.example"));
+ LoggerPtr level3 = hierarchy->getLogger(LOG4CXX_STR("com.example.app"));
+
+ level1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ level2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ level3->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+
+ level2->addAppender(appender2);
+ level2->setAdditivity(false);
+ level1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 0);
+ level2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ level3->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 2);
+ }
+
+ // Test 9: Three-level hierarchy - leaf removes additivity
+ void testThreeLevelHierarchyLeafRemovesAdditivity()
+ {
+ LoggerPtr level1 = hierarchy->getLogger(LOG4CXX_STR("com"));
+ LoggerPtr level2 = hierarchy->getLogger(LOG4CXX_STR("com.example"));
+ LoggerPtr level3 = hierarchy->getLogger(LOG4CXX_STR("com.example.app"));
+
+ level1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ level2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ level3->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+
+ level3->addAppender(appender2);
+ level3->setAdditivity(false);
+ level1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 0);
+ level2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 5);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 0);
+ level3->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 5);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ }
+
+ // Test 10: Four-level hierarchy - mixed additivity
+ void testFourLevelHierarchyMiddleRemovesAdditivity()
+ {
+ LoggerPtr level1 = hierarchy->getLogger(LOG4CXX_STR("org"));
+ LoggerPtr level2 = hierarchy->getLogger(LOG4CXX_STR("org.apache"));
+ LoggerPtr level3 = hierarchy->getLogger(LOG4CXX_STR("org.apache.log4cxx"));
+ LoggerPtr level4 = hierarchy->getLogger(LOG4CXX_STR("org.apache.log4cxx.test"));
+
+ level1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ level2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ level3->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ level4->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+
+ level1->addAppender(appender2);
+ level1->setAdditivity(false);
+ level4->addAppender(appender3);
+ level4->setAdditivity(false);
+ level1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ LOGUNIT_ASSERT_EQUAL(appender3->count, 0);
+ level2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 2);
+ LOGUNIT_ASSERT_EQUAL(appender3->count, 0);
+ level3->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender3->count, 0);
+ level4->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender3->count, 1);
+ }
+
+ // Test 11: Set parent from null to valid
+ void testSetParentFromNullToValid()
+ {
+ LoggerPtr parent = hierarchy->getLogger(LOG4CXX_STR("parent"));
+ LoggerPtr child = hierarchy->getLogger(LOG4CXX_STR("child"));
+ parent->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+
+ child->addAppender(appender2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+
+ child->changeParentTo(parent);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 2);
+ }
+
+ // Test 12: Change parent
+ void testSetParentChangeParent()
+ {
+ LoggerPtr parent1 = hierarchy->getLogger(LOG4CXX_STR("parent1"));
+ LoggerPtr parent2 = hierarchy->getLogger(LOG4CXX_STR("parent2"));
+ LoggerPtr child = hierarchy->getLogger(LOG4CXX_STR("child"));
+ parent1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ parent2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+
+ child->addAppender(appender2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+
+ child->changeParentTo(parent1);
+ parent1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 5);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ parent2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 6);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 7);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 2);
+
+ child->changeParentTo(parent2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 8);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+ }
+
+ // Test 13: Set parent multiple times
+ void testSetParentMultipleTimes()
+ {
+ LoggerPtr parent1 = hierarchy->getLogger(LOG4CXX_STR("p1"));
+ LoggerPtr parent2 = hierarchy->getLogger(LOG4CXX_STR("p2"));
+ LoggerPtr child = hierarchy->getLogger(LOG4CXX_STR("child"));
+
+ parent1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ parent2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+
+ child->changeParentTo(parent1);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 4);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 0);
+
+ child->changeParentTo(parent1);
+ parent1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 5);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 0);
+ parent2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 6);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 0);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 7);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 0);
+
+ child->changeParentTo(parent2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 8);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 0);
+
+ child->addAppender(appender2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 9);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+
+ child->changeParentTo(parent1);
+ parent1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 10);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ parent2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 11);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 12);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 2);
+
+ child->changeParentTo(parent2);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 13);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+
+ child->changeParentTo(rootLogger);
+ parent1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 14);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+ parent2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 15);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+ child->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 16);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 4);
+ }
+
+ // Test 14: Root level change propagates to children
+ void testRootAppenderChangePropagatesToChildren()
+ {
+ LoggerPtr child1 = hierarchy->getLogger(LOG4CXX_STR("child1"));
+ LoggerPtr child2 = hierarchy->getLogger(LOG4CXX_STR("child2"));
+ LoggerPtr grandchild = hierarchy->getLogger(LOG4CXX_STR("child1.grandchild"));
+
+ child1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 1);
+ child2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 2);
+ grandchild->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+
+ rootLogger->replaceAppender(appender1, appender2);
+ child1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 1);
+ child2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 2);
+ grandchild->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+
+ rootLogger->replaceAppender(appender2, appender3);
+ child1->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender3->count, 1);
+ child2->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender3->count, 2);
+ grandchild->addInfoEvent("message");
+ LOGUNIT_ASSERT_EQUAL(appender1->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender2->count, 3);
+ LOGUNIT_ASSERT_EQUAL(appender3->count, 3);
+ }
+
+};
+
+LOGUNIT_TEST_SUITE_REGISTRATION(LoggerAppenderCacheConsistencyTest);