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 44ad6d5a Provide a configuration file option to make a logger
asynchronous (#550)
44ad6d5a is described below
commit 44ad6d5a679883e3aef3eef0ead85978ea4afb5e
Author: Stephen Webb <[email protected]>
AuthorDate: Mon Oct 6 13:11:57 2025 +1100
Provide a configuration file option to make a logger asynchronous (#550)
* Document the 'asynchronous' configuration file attribute
* Add a change report entry
---
src/main/cpp/appenderskeleton.cpp | 5 ++
src/main/cpp/asyncappender.cpp | 2 +-
src/main/cpp/domconfigurator.cpp | 30 +++++++++++-
src/main/cpp/propertyconfigurator.cpp | 34 ++++++++++---
src/main/include/log4cxx/appenderskeleton.h | 1 +
src/main/include/log4cxx/asyncappender.h | 11 ++++-
src/site/markdown/change-report-gh.md | 2 +
src/site/markdown/configuration-samples.md | 18 ++++++-
src/test/cpp/asyncappendertestcase.cpp | 65 ++++++++++++++++++++++++-
src/test/resources/input/asyncLogger.properties | 20 ++++++++
src/test/resources/input/xml/asyncLogger.xml | 27 ++++++++++
11 files changed, 199 insertions(+), 16 deletions(-)
diff --git a/src/main/cpp/appenderskeleton.cpp
b/src/main/cpp/appenderskeleton.cpp
index f44b8ced..7f63cd0f 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/cpp/asyncappender.cpp b/src/main/cpp/asyncappender.cpp
index d199e392..35ae409a 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 7c940858..6983c9be 100644
--- a/src/main/cpp/domconfigurator.cpp
+++ b/src/main/cpp/domconfigurator.cpp
@@ -17,6 +17,7 @@
#include <log4cxx/logstring.h>
#include <log4cxx/xml/domconfigurator.h>
#include <log4cxx/appender.h>
+#include <log4cxx/asyncappender.h>
#include <log4cxx/layout.h>
#include <log4cxx/logger.h>
#include <log4cxx/logmanager.h>
@@ -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"
#define THRESHOLD_ATTR "threshold"
#define STRINGSTREAM_ATTR "stringstream"
#define CONFIG_DEBUG_ATTR "configDebug"
@@ -560,8 +562,18 @@ void DOMConfigurator::parseChildrenOfLoggerElement(
AppenderMap& appenders)
{
PropertySetter propSetter(logger);
- std::vector<AppenderPtr> newappenders;
+ 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<AsyncAppender>();
+ async->setName(loggerName);
+ }
+ std::vector<AppenderPtr> newappenders;
for (apr_xml_elem* currentElement = loggerElement->first_child;
currentElement;
currentElement = currentElement->next)
@@ -572,6 +584,8 @@ void DOMConfigurator::parseChildrenOfLoggerElement(
{
if (auto appender = findAppenderByReference(p,
utf8Decoder, currentElement, doc, appenders))
{
+ if (log4cxx::cast<AsyncAppender>(appender)) //
An explicitly configured AsyncAppender?
+ async.reset(); // Not required
if (LogLog::isDebugEnabled())
{
LogLog::debug(LOG4CXX_STR("Adding ") +
Appender::getStaticClass().getName()
@@ -579,6 +593,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 +610,17 @@ void DOMConfigurator::parseChildrenOfLoggerElement(
setParameter(p, utf8Decoder, currentElement,
propSetter);
}
}
- if (newappenders.empty())
+ 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;
+ }
+ else if (newappenders.empty())
logger->removeAllAppenders();
else
{
diff --git a/src/main/cpp/propertyconfigurator.cpp
b/src/main/cpp/propertyconfigurator.cpp
index b1c1d939..9c1fdd24 100644
--- a/src/main/cpp/propertyconfigurator.cpp
+++ b/src/main/cpp/propertyconfigurator.cpp
@@ -17,6 +17,7 @@
#include <log4cxx/logstring.h>
#include <log4cxx/propertyconfigurator.h>
+#include <log4cxx/asyncappender.h>
#include <log4cxx/helpers/properties.h>
#include <log4cxx/helpers/loglog.h>
#include <log4cxx/helpers/exception.h>
@@ -413,13 +414,18 @@ void PropertyConfigurator::parseLogger(
}
- AppenderPtr appender;
- LogString appenderName;
- std::vector<AppenderPtr> newappenders;
+ AsyncAppenderPtr async;
+ auto lsAsynchronous =
OptionConverter::findAndSubst(LOG4CXX_STR("log4j.asynchronous.") + loggerName,
props);
+ if (!lsAsynchronous.empty() &&
OptionConverter::toBoolean(lsAsynchronous, true))
+ {
+ async = std::make_shared<AsyncAppender>();
+ async->setName(loggerName);
+ }
+ std::vector<AppenderPtr> newappenders;
while (st.hasMoreTokens())
{
- appenderName = StringHelper::trim(st.nextToken());
+ auto appenderName = StringHelper::trim(st.nextToken());
if (appenderName.empty() || appenderName == LOG4CXX_STR(","))
{
@@ -431,18 +437,30 @@ 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<AsyncAppender>(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 && !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 );
}
AppenderPtr PropertyConfigurator::parseAppender(
diff --git a/src/main/include/log4cxx/appenderskeleton.h
b/src/main/include/log4cxx/appenderskeleton.h
index 3a47214d..30260371 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/main/include/log4cxx/asyncappender.h
b/src/main/include/log4cxx/asyncappender.h
index 406183d3..6e3212fe 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 <b>appender-ref</b> element in the logger configuration instead.
+
<b>Important notes:</b>
- 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/change-report-gh.md
b/src/site/markdown/change-report-gh.md
index 4b8b5354..7e4e4cca 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:
diff --git a/src/site/markdown/configuration-samples.md
b/src/site/markdown/configuration-samples.md
index 416f9f99..6a83e15d 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
<code>log4j.asynchronous.{LOGGER_NAME}=true</code> to your file
+- If using an XML file, add the <code>asynchronous="true"</code> attribute in
the <code>\<logger></code> 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:
</layout>
</appender>
- <root>
+ <root asynchronous="true" >
<priority value="info" />
<appender-ref ref="ConsoleAppender"/>
<appender-ref ref="FileAppender"/>
diff --git a/src/test/cpp/asyncappendertestcase.cpp
b/src/test/cpp/asyncappendertestcase.cpp
index 9ba7e173..f4e5d909 100644
--- a/src/test/cpp/asyncappendertestcase.cpp
+++ b/src/test/cpp/asyncappendertestcase.cpp
@@ -32,6 +32,7 @@
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/spi/location/locationinfo.h>
#include <log4cxx/xml/domconfigurator.h>
+#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/file.h>
#include <thread>
@@ -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<AsyncAppender>(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<VectorAppender>(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<AsyncAppender>(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<VectorAppender>(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 00000000..6ec9ef71
--- /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 00000000..cff50c18
--- /dev/null
+++ b/src/test/resources/input/xml/asyncLogger.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+<!--
+ 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:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
+ <appender name="VECTOR" class="org.apache.log4j.VectorAppender"/>
+ <root asynchronous="true" >
+ <level value="INFO"/>
+ <appender-ref ref="VECTOR" />
+ </root>
+</log4j:configuration>