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 ff453105 Allow removal of an individual logger from the single global
repository (#303)
ff453105 is described below
commit ff453105b54d895c87d93c0e82727d4484294ef8
Author: Stephen Webb <[email protected]>
AuthorDate: Fri Dec 1 10:02:51 2023 +1100
Allow removal of an individual logger from the single global repository
(#303)
* Document LoggerInstancePtr usage
---
src/main/cpp/hierarchy.cpp | 37 ++++++++-
src/main/cpp/logmanager.cpp | 8 ++
src/main/include/log4cxx/hierarchy.h | 13 ++++
src/main/include/log4cxx/loggerinstance.h | 125 ++++++++++++++++++++++++++++++
src/main/include/log4cxx/logmanager.h | 13 ++++
src/site/markdown/concepts.md | 12 +++
src/test/cpp/loggertestcase.cpp | 44 +++++++++++
7 files changed, 251 insertions(+), 1 deletion(-)
diff --git a/src/main/cpp/hierarchy.cpp b/src/main/cpp/hierarchy.cpp
index c304fab1..4d6725f8 100644
--- a/src/main/cpp/hierarchy.cpp
+++ b/src/main/cpp/hierarchy.cpp
@@ -258,7 +258,7 @@ LoggerPtr Hierarchy::getLogger(const LogString& name,
{
result = it->second;
}
- if (!result)
+ if (!result && factory)
{
LoggerPtr logger(factory->makeNewLoggerInstance(m_priv->pool,
name));
logger->setHierarchy(this);
@@ -489,3 +489,38 @@ void Hierarchy::addAppender(AppenderPtr appender)
{
m_priv->allAppenders.push_back(appender);
}
+
+bool Hierarchy::removeLogger(const LogString& name, bool ifNotUsed)
+{
+ auto parentRefCount = [this](const LoggerPtr& child) -> int
+ {
+ int result = 0;
+ for (auto& node : m_priv->provisionNodes)
+ {
+ if (node.second.end() != std::find(node.second.begin(),
node.second.end(), child))
+ ++result;
+ }
+ return result;
+ };
+ bool result = false;
+ std::lock_guard<std::recursive_mutex> lock(m_priv->mutex);
+ auto it = m_priv->loggers.find(name);
+ if (it == m_priv->loggers.end())
+ ;
+ else if (ifNotUsed && 1 + parentRefCount(it->second) <
it->second.use_count())
+ ;
+ else
+ {
+ for (auto& node : m_priv->provisionNodes)
+ {
+ for (size_t i = node.second.size(); 0 < i; )
+ {
+ if (node.second[--i] == it->second)
+ node.second.erase(node.second.begin() +
i);
+ }
+ }
+ m_priv->loggers.erase(it);
+ result = true;
+ }
+ return result;
+}
diff --git a/src/main/cpp/logmanager.cpp b/src/main/cpp/logmanager.cpp
index 16290758..ae03711a 100644
--- a/src/main/cpp/logmanager.cpp
+++ b/src/main/cpp/logmanager.cpp
@@ -210,3 +210,11 @@ void LogManager::resetConfiguration()
{
getLoggerRepository()->resetConfiguration();
}
+
+bool LogManager::removeLogger(const LogString& name, bool ifNotUsed)
+{
+ bool result = false;
+ if (auto r = dynamic_cast<Hierarchy*>(getLoggerRepository().get()))
+ result = r->removeLogger(name, ifNotUsed);
+ return result;
+}
diff --git a/src/main/include/log4cxx/hierarchy.h
b/src/main/include/log4cxx/hierarchy.h
index e43f63d2..e6cb9426 100644
--- a/src/main/include/log4cxx/hierarchy.h
+++ b/src/main/include/log4cxx/hierarchy.h
@@ -228,6 +228,19 @@ class LOG4CXX_EXPORT Hierarchy : public
spi::LoggerRepository
void addAppender(AppenderPtr appender);
+ /**
+ Remove the \c name Logger from the hierarchy.
+
+ Note: The \c name Logger must be retrieved from the hierarchy
+ \b after any subsequent configuration file change
+ for the newly loaded settings to be used.
+
+ @param name The logger to remove.
+ @param ifNotUsed If true and use_count() indicates there are
other references, do not remove the Logger and return false.
+ @returns true if \c name Logger was removed from the hierarchy.
+ */
+ bool removeLogger(const LogString& name, bool ifNotUsed = true);
+
private:
/**
diff --git a/src/main/include/log4cxx/loggerinstance.h
b/src/main/include/log4cxx/loggerinstance.h
new file mode 100644
index 00000000..b858bf1c
--- /dev/null
+++ b/src/main/include/log4cxx/loggerinstance.h
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+#ifndef LOG4CXX_LOGGER_INSTANCE_HDR_
+#define LOG4CXX_LOGGER_INSTANCE_HDR_
+
+#include <log4cxx/logmanager.h>
+#include <log4cxx/logger.h>
+
+namespace LOG4CXX_NS
+{
+
+/**
+ * A smart pointer (implicity convertable to LoggerPtr)
+ * that conditionally removes a Logger from the spi::LoggerRepository
+ * at the end of the instance variable's lifetime.
+
+ * If the configuration process loaded settings for the logger,
+ * or the logger is referenced elsewhere,
+ * the LoggerInstancePtr destructor will not remove it from the
spi::LoggerRepository.
+
+ * Use a LoggerInstancePtr to prevent unbounded growth
+ * of data in the spi::LoggerRepository
+ * when using runtime generated logger names.
+
+ * A runtime generated logger name is a technique for marking logging messages
+ * that allows control of the logger level at a class instance level (i.e. a
per object logger).
+
+ * A per object logger is useful when the object instance has a identifiable
name
+ * (e.g. when it is instantiated from configuration data).
+ */
+ class LoggerInstancePtr
+{
+ bool m_hadConfiguration; //!< Did the logger repository hold a \c
m_logger before creation of this instance?
+ LoggerPtr m_logger;
+public: // ...structors
+ /// A null LoggerPtr
+ LoggerInstancePtr() : m_hadConfiguration(false)
+ {}
+ /// A separately configurable logger named \c instanceName
+ template <class StringType>
+ LoggerInstancePtr(const StringType& instanceName)
+ : m_hadConfiguration(LogManager::exists(instanceName))
+ , m_logger(LogManager::getLogger(instanceName))
+ {
+ }
+ /// Conditionally remove the logger from the the spi::LoggerRepository
+ ~LoggerInstancePtr()
+ {
+ reset();
+ }
+
+ const LoggerPtr& operator->() const noexcept
+ {
+ return m_logger;
+ }
+
+ explicit operator bool() const noexcept
+ {
+ return !!m_logger;
+ }
+
+ operator LoggerPtr&() noexcept
+ {
+ return m_logger;
+ }
+
+ operator const LoggerPtr&() const noexcept
+ {
+ return m_logger;
+ }
+
+ Logger* get() noexcept
+ {
+ return m_logger.get();
+ }
+
+ const Logger* get() const noexcept
+ {
+ return m_logger.get();
+ }
+
+ /// Conditionally remove the Logger from the spi::LoggerRepository
+ void reset()
+ {
+ if (m_logger && !m_hadConfiguration)
+ {
+ auto name = m_logger->getName();
+ m_logger.reset(); // Decrease reference count
+ LogManager::removeLogger(name);
+ }
+ else
+ {
+ m_hadConfiguration = false;
+ m_logger.reset();
+ }
+ }
+
+ /// Change this to a logger named \c instanceName
+ template <class StringType>
+ void reset(const StringType& instanceName)
+ {
+ reset();
+ m_hadConfiguration = !!LogManager::exists(instanceName);
+ m_logger = LogManager::getLogger(instanceName);
+ }
+};
+
+} // namespace LOG4CXX_NS
+
+#endif // LOG4CXX_LOGGER_INSTANCE_HDR_
diff --git a/src/main/include/log4cxx/logmanager.h
b/src/main/include/log4cxx/logmanager.h
index 5af0949d..65f77a8a 100644
--- a/src/main/include/log4cxx/logmanager.h
+++ b/src/main/include/log4cxx/logmanager.h
@@ -223,6 +223,19 @@ class LOG4CXX_EXPORT LogManager
to their default.
*/
static void resetConfiguration();
+
+ /**
+ Remove the \c name Logger from the hierarchy.
+
+ Note: The \c name Logger must be retrieved from the hierarchy
+ \b after any subsequent configuration file change
+ for the newly loaded settings to be used.
+
+ @param name The logger to remove.
+ @param ifNotUsed If true and use_count() indicates there are
other references, do not remove the Logger and return false.
+ @returns true if \c name Logger was removed from the hierarchy.
+ */
+ static bool removeLogger(const LogString& name, bool ifNotUsed
= true);
}; // class LogManager
} // namespace log4cxx
diff --git a/src/site/markdown/concepts.md b/src/site/markdown/concepts.md
index bb3e195c..e0abec0c 100644
--- a/src/site/markdown/concepts.md
+++ b/src/site/markdown/concepts.md
@@ -44,6 +44,8 @@ but any category naming scheme may be used.
Logging category names (or equivalently logger name)
are case-sensitive.
+## Naming {#naming}
+
Log4cxx makes it easy to name loggers by *software component*. This can
be accomplished by statically instantiating a logger in each class, with
the logger name equal to the fully qualified name of the class. This is
@@ -68,6 +70,16 @@ named `com.foo.Bar`. Similarly, `java` is a parent of
`java.util`
and an ancestor of `java.util.Vector`. This naming scheme should be
familiar to most developers.
+Sometimes a per object logger is useful.
+When each class instance has a identifiable name
+(e.g. when it is instantiated from configuration data)
+add a member variable to hold a log4cxx::LoggerInstancePtr
+and initialize it with a name that makes it a *descendant* of the class.
+This allows activation of DEBUG logging for a single object
+or all objects of that class.
+
+## Instantiation {#getLogger}
+
The root logger resides at the top of the hierarchy. It is
exceptional in two ways:
diff --git a/src/test/cpp/loggertestcase.cpp b/src/test/cpp/loggertestcase.cpp
index e18f6897..9579721d 100644
--- a/src/test/cpp/loggertestcase.cpp
+++ b/src/test/cpp/loggertestcase.cpp
@@ -23,6 +23,7 @@
#include <log4cxx/logmanager.h>
#include <log4cxx/level.h>
#include <log4cxx/hierarchy.h>
+#include <log4cxx/loggerinstance.h>
#include <log4cxx/spi/rootlogger.h>
#include <log4cxx/helpers/propertyresourcebundle.h>
#include "insertwide.h"
@@ -99,6 +100,7 @@ LOGUNIT_CLASS(LoggerTestCase)
// LOGUNIT_TEST(testRB3);
LOGUNIT_TEST(testExists);
LOGUNIT_TEST(testHierarchy1);
+ LOGUNIT_TEST(testLoggerInstance);
LOGUNIT_TEST(testTrace);
LOGUNIT_TEST(testIsTraceEnabled);
LOGUNIT_TEST(testAddingListeners);
@@ -449,6 +451,48 @@ public:
LOGUNIT_ASSERT_EQUAL(true, Logger::isTraceEnabledFor(abc));
}
+ void testLoggerInstance()
+ {
+ LoggerInstancePtr initiallyNull;
+ auto ca = std::make_shared<CountingAppender>();
+ Logger::getRootLogger()->addAppender(ca);
+
+ // Check instance loggers are removed from the LoggerRepository
+ std::vector<LogString> instanceNames =
+ { LOG4CXX_STR("xxx.zzz")
+ , LOG4CXX_STR("xxx.aaaa")
+ , LOG4CXX_STR("xxx.bbb")
+ , LOG4CXX_STR("xxx.ccc")
+ , LOG4CXX_STR("xxx.ddd")
+ };
+ auto initialCount = LogManager::getCurrentLoggers().size();
+ int expectedCount = 0;
+ for (auto loggerName : instanceNames)
+ {
+ LoggerInstancePtr instanceLogger(loggerName);
+ instanceLogger->info("Hello, World.");
+ ++expectedCount;
+ }
+ auto finalCount = LogManager::getCurrentLoggers().size();
+ LOGUNIT_ASSERT_EQUAL(initialCount, finalCount);
+ LOGUNIT_ASSERT_EQUAL(ca->counter, expectedCount);
+
+ // Check the logger is not removed when referenced elsewhere
+ auto a = Logger::getLogger(LOG4CXX_TEST_STR("xxx.aaaa"));
+ {
+ LoggerInstancePtr
instanceLogger(LOG4CXX_TEST_STR("xxx.aaaa"));
+ instanceLogger->info("Hello, World.");
+ ++expectedCount;
+ }
+
LOGUNIT_ASSERT(LogManager::exists(LOG4CXX_TEST_STR("xxx.aaaa")));
+ LOGUNIT_ASSERT_EQUAL(ca->counter, expectedCount);
+
+ // Check reset
+ initiallyNull.reset("InitiallyNullLoggerPtr");
+ LOGUNIT_ASSERT(initiallyNull);
+ LOG4CXX_INFO(initiallyNull, "Hello, World.");
+ }
+
void compileTestForLOGCXX202() const
{
//