From 4b94a352aa3853bb9e21fc3a19f35389a39df8ca Mon Sep 17 00:00:00 2001 From: Stephen Webb Date: Sat, 4 Oct 2025 16:59:41 +1000 Subject: [PATCH 1/8] Provide a configuration file option to make all logging asynchronous --- src/main/cpp/domconfigurator.cpp | 29 ++++++++++++++++++++++- src/main/cpp/propertyconfigurator.cpp | 34 +++++++++++++++++++++------ 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/main/cpp/domconfigurator.cpp b/src/main/cpp/domconfigurator.cpp index 7c9408586..f54fcd71b 100644 --- a/src/main/cpp/domconfigurator.cpp +++ b/src/main/cpp/domconfigurator.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +133,7 @@ IMPLEMENT_LOG4CXX_OBJECT(DOMConfigurator) #define ERROR_HANDLER_TAG "errorHandler" #define REF_ATTR "ref" #define ADDITIVITY_ATTR "additivity" +#define ASYNCHRONOUS_ATTR "asynchronous-logging" #define THRESHOLD_ATTR "threshold" #define STRINGSTREAM_ATTR "stringstream" #define CONFIG_DEBUG_ATTR "configDebug" @@ -561,6 +563,10 @@ void DOMConfigurator::parseChildrenOfLoggerElement( { PropertySetter propSetter(logger); std::vector newappenders; + AsyncAppenderPtr async; + auto pAppender = appenders.find(LOG4CXX_STR(ASYNCHRONOUS_ATTR)); + if (appenders.end() != pAppender) + async = log4cxx::cast(pAppender->second); for (apr_xml_elem* currentElement = loggerElement->first_child; currentElement; @@ -572,6 +578,8 @@ void DOMConfigurator::parseChildrenOfLoggerElement( { if (auto appender = findAppenderByReference(p, utf8Decoder, currentElement, doc, appenders)) { + if (log4cxx::cast(appender)) // An explicitly configured AsyncAppender? + async.reset(); // Not required if (LogLog::isDebugEnabled()) { LogLog::debug(LOG4CXX_STR("Adding ") + Appender::getStaticClass().getName() @@ -579,6 +587,8 @@ void DOMConfigurator::parseChildrenOfLoggerElement( + LOG4CXX_STR(" to logger [") + logger->getName() + LOG4CXX_STR("]")); } newappenders.push_back(appender); + if (async) + async->addAppender(appender); } } else if (tagName == LEVEL_TAG) @@ -594,7 +604,12 @@ void DOMConfigurator::parseChildrenOfLoggerElement( setParameter(p, utf8Decoder, currentElement, propSetter); } } - if (newappenders.empty()) + if (async) + { + logger->replaceAppenders({async}); + m_priv->appenderAdded = true; + } + else if (newappenders.empty()) logger->removeAllAppenders(); else { @@ -1087,6 +1102,18 @@ void DOMConfigurator::parse( } } + auto lsAsynchronous = subst(getAttribute(utf8Decoder, element, ASYNCHRONOUS_ATTR)); + if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) + { + appenders[LOG4CXX_STR(ASYNCHRONOUS_ATTR)] = std::make_shared(); + if (LogLog::isDebugEnabled()) + { + LogLog::debug(LOG4CXX_STR("Asynchronous logging =[") + + LogString(LOG4CXX_STR("true")) + + LOG4CXX_STR("]")); + } + } + LogString threadSignalValue = subst(getAttribute(utf8Decoder, element, THREAD_CONFIG_ATTR)); if ( !threadSignalValue.empty() && threadSignalValue != LOG4CXX_STR("NULL") ) diff --git a/src/main/cpp/propertyconfigurator.cpp b/src/main/cpp/propertyconfigurator.cpp index b1c1d939d..95b7902d9 100644 --- a/src/main/cpp/propertyconfigurator.cpp +++ b/src/main/cpp/propertyconfigurator.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -231,6 +232,18 @@ spi::ConfigurationStatus PropertyConfigurator::doConfigure(helpers::Properties& } } + auto lsAsynchronous = OptionConverter::findAndSubst(LOG4CXX_STR("log4j.asynchronous-logging"), properties); + if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) + { + (*m_priv->registry)[LOG4CXX_STR("log4j.asynchronous-logging")] = std::make_shared(); + if (LogLog::isDebugEnabled()) + { + LogLog::debug(LOG4CXX_STR("Asynchronous logging =[") + + LogString(LOG4CXX_STR("true")) + + LOG4CXX_STR("]")); + } + } + LogString threadConfigurationValue(properties.getProperty(LOG4CXX_STR("log4j.threadConfiguration"))); if ( threadConfigurationValue == LOG4CXX_STR("NoConfiguration") ) @@ -413,13 +426,15 @@ void PropertyConfigurator::parseLogger( } - AppenderPtr appender; - LogString appenderName; std::vector newappenders; + AsyncAppenderPtr async; + auto pAppender = m_priv->registry->find(LOG4CXX_STR("log4j.asynchronous-logging")); + if (m_priv->registry->end() != pAppender) + async = log4cxx::cast(pAppender->second); while (st.hasMoreTokens()) { - appenderName = StringHelper::trim(st.nextToken()); + auto appenderName = StringHelper::trim(st.nextToken()); if (appenderName.empty() || appenderName == LOG4CXX_STR(",")) { @@ -431,18 +446,23 @@ void PropertyConfigurator::parseLogger( LogLog::debug(LOG4CXX_STR("Parsing ") + Appender::getStaticClass().getName() + LOG4CXX_STR(" named [") + appenderName + LOG4CXX_STR("]")); } - appender = parseAppender(props, appenderName); - - if (appender != 0) + if (auto appender = parseAppender(props, appenderName)) { newappenders.push_back(appender); + if (log4cxx::cast(appender)) // An explicitly configured AsyncAppender? + async.reset(); // Not required + if (async) + async->addAppender(appender); } } #if 15 < LOG4CXX_ABI_VERSION if (!newappenders.empty()) m_priv->appenderAdded = true; #endif - logger->reconfigure( newappenders, additivity ); + if (async) + logger->reconfigure( {async}, additivity ); + else + logger->reconfigure( newappenders, additivity ); } AppenderPtr PropertyConfigurator::parseAppender( From 3367895f876f42219cfb0208f2d9e56ada40b2e3 Mon Sep 17 00:00:00 2001 From: Stephen Webb Date: Sat, 4 Oct 2025 17:25:43 +1000 Subject: [PATCH 2/8] Fix compilation when LogString != std::string --- src/main/cpp/domconfigurator.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/cpp/domconfigurator.cpp b/src/main/cpp/domconfigurator.cpp index f54fcd71b..41bef4fbf 100644 --- a/src/main/cpp/domconfigurator.cpp +++ b/src/main/cpp/domconfigurator.cpp @@ -564,7 +564,9 @@ void DOMConfigurator::parseChildrenOfLoggerElement( PropertySetter propSetter(logger); std::vector newappenders; AsyncAppenderPtr async; - auto pAppender = appenders.find(LOG4CXX_STR(ASYNCHRONOUS_ATTR)); + std::string sAsyncAttr{ ASYNCHRONOUS_ATTR }; + LOG4CXX_DECODE_CHAR(lsAsyncAttr, sAsyncAttr); + auto pAppender = appenders.find(lsAsyncAttr); if (appenders.end() != pAppender) async = log4cxx::cast(pAppender->second); @@ -1102,10 +1104,12 @@ void DOMConfigurator::parse( } } - auto lsAsynchronous = subst(getAttribute(utf8Decoder, element, ASYNCHRONOUS_ATTR)); + std::string sAsyncAttr{ ASYNCHRONOUS_ATTR }; + auto lsAsynchronous = subst(getAttribute(utf8Decoder, element, sAsyncAttr)); if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) { - appenders[LOG4CXX_STR(ASYNCHRONOUS_ATTR)] = std::make_shared(); + LOG4CXX_DECODE_CHAR(lsAsyncAttr, sAsyncAttr); + appenders[lsAsyncAttr] = std::make_shared(); if (LogLog::isDebugEnabled()) { LogLog::debug(LOG4CXX_STR("Asynchronous logging =[") From 5a0f1da8765318a0a17f364989a9487f20d3108a Mon Sep 17 00:00:00 2001 From: Stephen Webb Date: Sat, 4 Oct 2025 18:08:31 +1000 Subject: [PATCH 3/8] Asynchronous logging is only relevent for a logger with attached appenders --- src/main/cpp/domconfigurator.cpp | 31 +++++++++------------------ src/main/cpp/propertyconfigurator.cpp | 30 +++++++++++--------------- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/main/cpp/domconfigurator.cpp b/src/main/cpp/domconfigurator.cpp index 41bef4fbf..76c1d6663 100644 --- a/src/main/cpp/domconfigurator.cpp +++ b/src/main/cpp/domconfigurator.cpp @@ -562,14 +562,12 @@ void DOMConfigurator::parseChildrenOfLoggerElement( AppenderMap& appenders) { PropertySetter propSetter(logger); - std::vector newappenders; AsyncAppenderPtr async; - std::string sAsyncAttr{ ASYNCHRONOUS_ATTR }; - LOG4CXX_DECODE_CHAR(lsAsyncAttr, sAsyncAttr); - auto pAppender = appenders.find(lsAsyncAttr); - if (appenders.end() != pAppender) - async = log4cxx::cast(pAppender->second); + auto lsAsynchronous = subst(getAttribute(utf8Decoder, loggerElement, ASYNCHRONOUS_ATTR)); + if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) + async = std::make_shared(); + std::vector newappenders; for (apr_xml_elem* currentElement = loggerElement->first_child; currentElement; currentElement = currentElement->next) @@ -606,8 +604,13 @@ void DOMConfigurator::parseChildrenOfLoggerElement( setParameter(p, utf8Decoder, currentElement, propSetter); } } - if (async) + if (async && !newappenders.empty()) { + if (LogLog::isDebugEnabled()) + { + LogLog::debug(LOG4CXX_STR("Asynchronous logging for [") + + logger->getName() + LOG4CXX_STR("] is on")); + } logger->replaceAppenders({async}); m_priv->appenderAdded = true; } @@ -1104,20 +1107,6 @@ void DOMConfigurator::parse( } } - std::string sAsyncAttr{ ASYNCHRONOUS_ATTR }; - auto lsAsynchronous = subst(getAttribute(utf8Decoder, element, sAsyncAttr)); - if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) - { - LOG4CXX_DECODE_CHAR(lsAsyncAttr, sAsyncAttr); - appenders[lsAsyncAttr] = std::make_shared(); - if (LogLog::isDebugEnabled()) - { - LogLog::debug(LOG4CXX_STR("Asynchronous logging =[") - + LogString(LOG4CXX_STR("true")) - + LOG4CXX_STR("]")); - } - } - LogString threadSignalValue = subst(getAttribute(utf8Decoder, element, THREAD_CONFIG_ATTR)); if ( !threadSignalValue.empty() && threadSignalValue != LOG4CXX_STR("NULL") ) diff --git a/src/main/cpp/propertyconfigurator.cpp b/src/main/cpp/propertyconfigurator.cpp index 95b7902d9..6615ea227 100644 --- a/src/main/cpp/propertyconfigurator.cpp +++ b/src/main/cpp/propertyconfigurator.cpp @@ -232,18 +232,6 @@ spi::ConfigurationStatus PropertyConfigurator::doConfigure(helpers::Properties& } } - auto lsAsynchronous = OptionConverter::findAndSubst(LOG4CXX_STR("log4j.asynchronous-logging"), properties); - if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) - { - (*m_priv->registry)[LOG4CXX_STR("log4j.asynchronous-logging")] = std::make_shared(); - if (LogLog::isDebugEnabled()) - { - LogLog::debug(LOG4CXX_STR("Asynchronous logging =[") - + LogString(LOG4CXX_STR("true")) - + LOG4CXX_STR("]")); - } - } - LogString threadConfigurationValue(properties.getProperty(LOG4CXX_STR("log4j.threadConfiguration"))); if ( threadConfigurationValue == LOG4CXX_STR("NoConfiguration") ) @@ -426,12 +414,13 @@ void PropertyConfigurator::parseLogger( } - std::vector newappenders; + AsyncAppenderPtr async; - auto pAppender = m_priv->registry->find(LOG4CXX_STR("log4j.asynchronous-logging")); - if (m_priv->registry->end() != pAppender) - async = log4cxx::cast(pAppender->second); + auto lsAsynchronous = OptionConverter::findAndSubst(LOG4CXX_STR("log4j.asynchronous-logging.") + loggerName, props); + if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) + async = std::make_shared(); + std::vector newappenders; while (st.hasMoreTokens()) { auto appenderName = StringHelper::trim(st.nextToken()); @@ -459,8 +448,15 @@ void PropertyConfigurator::parseLogger( if (!newappenders.empty()) m_priv->appenderAdded = true; #endif - if (async) + if (async && !newappenders.empty()) + { + if (LogLog::isDebugEnabled()) + { + LogLog::debug(LOG4CXX_STR("Asynchronous logging for [") + + loggerName + LOG4CXX_STR("] is on")); + } logger->reconfigure( {async}, additivity ); + } else logger->reconfigure( newappenders, additivity ); } From fc6d58696d8e3426655c1ccbac83e916a82610b3 Mon Sep 17 00:00:00 2001 From: Stephen Webb Date: Sat, 4 Oct 2025 22:23:43 +1000 Subject: [PATCH 4/8] Improve naming --- src/main/cpp/domconfigurator.cpp | 2 +- src/main/cpp/propertyconfigurator.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/cpp/domconfigurator.cpp b/src/main/cpp/domconfigurator.cpp index 76c1d6663..e0e007331 100644 --- a/src/main/cpp/domconfigurator.cpp +++ b/src/main/cpp/domconfigurator.cpp @@ -133,7 +133,7 @@ IMPLEMENT_LOG4CXX_OBJECT(DOMConfigurator) #define ERROR_HANDLER_TAG "errorHandler" #define REF_ATTR "ref" #define ADDITIVITY_ATTR "additivity" -#define ASYNCHRONOUS_ATTR "asynchronous-logging" +#define ASYNCHRONOUS_ATTR "asynchronous" #define THRESHOLD_ATTR "threshold" #define STRINGSTREAM_ATTR "stringstream" #define CONFIG_DEBUG_ATTR "configDebug" diff --git a/src/main/cpp/propertyconfigurator.cpp b/src/main/cpp/propertyconfigurator.cpp index 6615ea227..7e8438818 100644 --- a/src/main/cpp/propertyconfigurator.cpp +++ b/src/main/cpp/propertyconfigurator.cpp @@ -416,7 +416,7 @@ void PropertyConfigurator::parseLogger( AsyncAppenderPtr async; - auto lsAsynchronous = OptionConverter::findAndSubst(LOG4CXX_STR("log4j.asynchronous-logging.") + loggerName, props); + auto lsAsynchronous = OptionConverter::findAndSubst(LOG4CXX_STR("log4j.asynchronous.") + loggerName, props); if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) async = std::make_shared(); From 78bcacacb88bf42426e82e1ed2b78c41119f138f Mon Sep 17 00:00:00 2001 From: Stephen Webb Date: Sun, 5 Oct 2025 13:38:21 +1100 Subject: [PATCH 5/8] Name the Logger specific AsyncAppenders --- src/main/cpp/asyncappender.cpp | 2 +- src/main/cpp/domconfigurator.cpp | 6 ++++++ src/main/cpp/propertyconfigurator.cpp | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/cpp/asyncappender.cpp b/src/main/cpp/asyncappender.cpp index d199e392c..35ae409a4 100644 --- a/src/main/cpp/asyncappender.cpp +++ b/src/main/cpp/asyncappender.cpp @@ -599,7 +599,7 @@ void AsyncAppender::dispatch() if (LogLog::isDebugEnabled()) { Pool p; - LogString msg(LOG4CXX_STR("AsyncAppender")); + LogString msg(LOG4CXX_STR("[") + getName() + LOG4CXX_STR("] AsyncAppender")); #ifdef _DEBUG msg += LOG4CXX_STR(" iterationCount "); StringHelper::toString(iterationCount, p, msg); diff --git a/src/main/cpp/domconfigurator.cpp b/src/main/cpp/domconfigurator.cpp index e0e007331..6983c9be6 100644 --- a/src/main/cpp/domconfigurator.cpp +++ b/src/main/cpp/domconfigurator.cpp @@ -562,10 +562,16 @@ void DOMConfigurator::parseChildrenOfLoggerElement( AppenderMap& appenders) { PropertySetter propSetter(logger); + auto loggerName = m_priv->repository->getRootLogger() == logger + ? LogString(LOG4CXX_STR("root")) + : logger->getName(); AsyncAppenderPtr async; auto lsAsynchronous = subst(getAttribute(utf8Decoder, loggerElement, ASYNCHRONOUS_ATTR)); if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) + { async = std::make_shared(); + async->setName(loggerName); + } std::vector newappenders; for (apr_xml_elem* currentElement = loggerElement->first_child; diff --git a/src/main/cpp/propertyconfigurator.cpp b/src/main/cpp/propertyconfigurator.cpp index 7e8438818..9c1fdd24c 100644 --- a/src/main/cpp/propertyconfigurator.cpp +++ b/src/main/cpp/propertyconfigurator.cpp @@ -414,11 +414,13 @@ void PropertyConfigurator::parseLogger( } - AsyncAppenderPtr async; auto lsAsynchronous = OptionConverter::findAndSubst(LOG4CXX_STR("log4j.asynchronous.") + loggerName, props); if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true)) + { async = std::make_shared(); + async->setName(loggerName); + } std::vector newappenders; while (st.hasMoreTokens()) From 7ac2660b833b7c96a0e18f87a4f5dc2282cc55dd Mon Sep 17 00:00:00 2001 From: Stephen Webb Date: Sun, 5 Oct 2025 14:29:49 +1100 Subject: [PATCH 6/8] Tests for making root logger asynchronous --- src/main/cpp/appenderskeleton.cpp | 5 ++ src/main/include/log4cxx/appenderskeleton.h | 1 + src/test/cpp/asyncappendertestcase.cpp | 65 ++++++++++++++++++- .../resources/input/asyncLogger.properties | 20 ++++++ src/test/resources/input/xml/asyncLogger.xml | 27 ++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/input/asyncLogger.properties create mode 100644 src/test/resources/input/xml/asyncLogger.xml diff --git a/src/main/cpp/appenderskeleton.cpp b/src/main/cpp/appenderskeleton.cpp index f44b8cedd..7f63cd0fe 100644 --- a/src/main/cpp/appenderskeleton.cpp +++ b/src/main/cpp/appenderskeleton.cpp @@ -191,6 +191,11 @@ void AppenderSkeleton::setOption(const LogString& option, { setThreshold(Level::toLevelLS(value)); } + else if (StringHelper::equalsIgnoreCase(option, + LOG4CXX_STR("NAME"), LOG4CXX_STR("name"))) + { + setName(value); + } } const spi::ErrorHandlerPtr AppenderSkeleton::getErrorHandler() const diff --git a/src/main/include/log4cxx/appenderskeleton.h b/src/main/include/log4cxx/appenderskeleton.h index 3a47214d3..302603719 100644 --- a/src/main/include/log4cxx/appenderskeleton.h +++ b/src/main/include/log4cxx/appenderskeleton.h @@ -92,6 +92,7 @@ class LOG4CXX_EXPORT AppenderSkeleton : Supported options | Supported values | Default value | -------------- | ---------------- | --------------- | + Name | {any} | - | Threshold | Trace,Debug,Info,Warn,Error,Fatal,Off,All | All | */ void setOption(const LogString& option, const LogString& value) override; diff --git a/src/test/cpp/asyncappendertestcase.cpp b/src/test/cpp/asyncappendertestcase.cpp index 9ba7e173a..f4e5d9099 100644 --- a/src/test/cpp/asyncappendertestcase.cpp +++ b/src/test/cpp/asyncappendertestcase.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -138,8 +139,10 @@ class AsyncAppenderTestCase : public AppenderSkeletonTestCase LOGUNIT_TEST(testBufferOverflowBehavior); LOGUNIT_TEST(testLoggingAppender); #if LOG4CXX_HAS_DOMCONFIGURATOR - LOGUNIT_TEST(testConfiguration); + LOGUNIT_TEST(testXMLConfiguration); + LOGUNIT_TEST(testAsyncLoggerXML); #endif + LOGUNIT_TEST(testAsyncLoggerProperties); LOGUNIT_TEST_SUITE_END(); #ifdef _DEBUG @@ -531,7 +534,7 @@ class AsyncAppenderTestCase : public AppenderSkeletonTestCase } #if LOG4CXX_HAS_DOMCONFIGURATOR - void testConfiguration() + void testXMLConfiguration() { // Configure Log4cxx auto status = xml::DOMConfigurator::configure("input/xml/asyncAppender1.xml"); @@ -562,8 +565,66 @@ class AsyncAppenderTestCase : public AppenderSkeletonTestCase LOGUNIT_ASSERT_EQUAL(LEN, v.size()); LOGUNIT_ASSERT(vectorAppender->isClosed()); } + + void testAsyncLoggerXML() + { + // Configure Log4cxx + auto status = xml::DOMConfigurator::configure("input/xml/asyncLogger.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::cast(appenders.front()); + LOGUNIT_ASSERT(asyncAppender); + + // Log some messages + size_t LEN = 20; + for (size_t i = 0; i < LEN; i++) + { + LOG4CXX_INFO_ASYNC(root, "message" << i); + } + asyncAppender->close(); + + // Check all message were received + auto vectorAppender = log4cxx::cast(asyncAppender->getAppender(LOG4CXX_STR("VECTOR"))); + LOGUNIT_ASSERT(vectorAppender); + auto& v = vectorAppender->getVector(); + LOGUNIT_ASSERT_EQUAL(LEN, v.size()); + LOGUNIT_ASSERT(vectorAppender->isClosed()); + } #endif + void testAsyncLoggerProperties() + { + // Configure Log4cxx + auto status = PropertyConfigurator::configure("input/asyncLogger.properties"); + 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::cast(appenders.front()); + LOGUNIT_ASSERT(asyncAppender); + + // Log some messages + size_t LEN = 20; + for (size_t i = 0; i < LEN; i++) + { + LOG4CXX_INFO_ASYNC(root, "message" << i); + } + asyncAppender->close(); + + // Check all message were received + auto vectorAppender = log4cxx::cast(asyncAppender->getAppender(LOG4CXX_STR("VECTOR"))); + LOGUNIT_ASSERT(vectorAppender); + auto& v = vectorAppender->getVector(); + LOGUNIT_ASSERT_EQUAL(LEN, v.size()); + LOGUNIT_ASSERT(vectorAppender->isClosed()); + } + }; diff --git a/src/test/resources/input/asyncLogger.properties b/src/test/resources/input/asyncLogger.properties new file mode 100644 index 000000000..6ec9ef719 --- /dev/null +++ b/src/test/resources/input/asyncLogger.properties @@ -0,0 +1,20 @@ +# 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. +# +log4j.rootCategory=INFO, A1 +log4j.asynchronous.root=true + +log4j.appender.A1=org.apache.log4j.VectorAppender +log4j.appender.A1.name=VECTOR \ No newline at end of file diff --git a/src/test/resources/input/xml/asyncLogger.xml b/src/test/resources/input/xml/asyncLogger.xml new file mode 100644 index 000000000..cff50c181 --- /dev/null +++ b/src/test/resources/input/xml/asyncLogger.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + From 1ad9f5a93296d22f200c292db8ae0b3a5204371f Mon Sep 17 00:00:00 2001 From: Stephen Webb Date: Sun, 5 Oct 2025 15:55:08 +1100 Subject: [PATCH 7/8] Document the 'asynchronous' configuration file attribute --- src/main/include/log4cxx/asyncappender.h | 11 +++++++++-- src/site/markdown/configuration-samples.md | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/include/log4cxx/asyncappender.h b/src/main/include/log4cxx/asyncappender.h index 406183d34..6e3212fec 100644 --- a/src/main/include/log4cxx/asyncappender.h +++ b/src/main/include/log4cxx/asyncappender.h @@ -34,14 +34,21 @@ The AsyncAppender stores the logging event in a bounded buffer and then returns control to the application. A separate thread forwards events to the attached appender(s). +An AsyncAppender is used when you configure a logger to be asynchronous. +These AsyncAppender(s) use [the default values](@ref log4cxx::AsyncAppender::setOption) for all options +and they cannot be changed using configuration file entries. +For more control over the AsyncAppender options, +use appender-ref element in the logger configuration instead. + Important notes: - Your application must call LogManager::shutdown when it exits to prevent undefined behaviour when using this appender. -- Runtime configuration requires an XML configuration file +- Runtime configuration of options requires an XML configuration file (see the example below). This appender is useful when outputting to a slow event sink, -for example, a remote SMTP server or a database. +for example, unbuffered output to a file, +a remote SMTP server or a database. Note that configuring a FileAppender to use [buffered output](@ref log4cxx::FileAppender::setOption) usually results in lower overhead than attaching the FileAppender to an AsyncAppender diff --git a/src/site/markdown/configuration-samples.md b/src/site/markdown/configuration-samples.md index 416f9f99f..6a83e15d3 100644 --- a/src/site/markdown/configuration-samples.md +++ b/src/site/markdown/configuration-samples.md @@ -86,6 +86,21 @@ The variable names are [documented here](@ref log4cxx.spi.Configurator.propertie To check the correct values are used when your configuration file is loaded, use [Log4cxx internal debugging]. +# Configuring a logger to use asynchronous output + +Log4cxx 1.6 allows you to more easily attach an [AsyncAppender](@ref log4cxx.AsyncAppender) +to a logger using a configuration file. +- If using a properties file, add the line log4j.asynchronous.{LOGGER_NAME}=true to your file +- If using an XML file, add the asynchronous="true" attribute in the \ element. + +The "asynchronous" attribute is only relevent for a logger with attached appenders. +The attribute is ignored if the logger does not have any directly attached appenders. + +The "asynchronous" attribute results in the configured appender(s) +being attached an [AsyncAppender](@ref log4cxx.AsyncAppender) +and it is the [AsyncAppender](@ref log4cxx.AsyncAppender) +that is attached directly to the logger. + # Configuration Samples {#configuration-samples} The following snippets show various ways of configuring Log4cxx. @@ -124,6 +139,7 @@ to store a log file per executable in a product related logs directory: ~~~{.properties} # Uncomment a line to enable debugging for a category log4j.rootCategory=INFO, A1 +log4j.asynchronous.root=true log4j.appender.A1=org.apache.log4j.RollingFileAppender log4j.appender.A1.MaxFileSize=5MB @@ -286,7 +302,7 @@ to store a log file per executable in a product related logs directory: - + From 52aa575a3873dd17fd7f8c1fe1c921e6f6f48eef Mon Sep 17 00:00:00 2001 From: Stephen Webb Date: Sun, 5 Oct 2025 15:59:01 +1100 Subject: [PATCH 8/8] Add a change report entry --- src/site/markdown/change-report-gh.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/site/markdown/change-report-gh.md b/src/site/markdown/change-report-gh.md index 4b8b53545..7e4e4ccab 100644 --- a/src/site/markdown/change-report-gh.md +++ b/src/site/markdown/change-report-gh.md @@ -63,6 +63,8 @@ and the LOG4CXX_CONFIGURATION environment variable (see log4cxx::spi::Configurat \[[#529](https://github.com/apache/logging-log4cxx/pull/529)\] * New logging macros that defer binary-to-text conversion until used in AsyncAppender's background thread \[[#548](https://github.com/apache/logging-log4cxx/pull/548)\] +* A simplified way to attach an AsyncAppender to a logger using a configuration file + \[[#550](https://github.com/apache/logging-log4cxx/pull/550)\] The following issues have been addressed: