Skip to content

Commit 44ad6d5

Browse files
authored
Provide a configuration file option to make a logger asynchronous (#550)
* Document the 'asynchronous' configuration file attribute * Add a change report entry
1 parent db966ad commit 44ad6d5

File tree

11 files changed

+199
-16
lines changed

11 files changed

+199
-16
lines changed

src/main/cpp/appenderskeleton.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ void AppenderSkeleton::setOption(const LogString& option,
191191
{
192192
setThreshold(Level::toLevelLS(value));
193193
}
194+
else if (StringHelper::equalsIgnoreCase(option,
195+
LOG4CXX_STR("NAME"), LOG4CXX_STR("name")))
196+
{
197+
setName(value);
198+
}
194199
}
195200

196201
const spi::ErrorHandlerPtr AppenderSkeleton::getErrorHandler() const

src/main/cpp/asyncappender.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ void AsyncAppender::dispatch()
599599
if (LogLog::isDebugEnabled())
600600
{
601601
Pool p;
602-
LogString msg(LOG4CXX_STR("AsyncAppender"));
602+
LogString msg(LOG4CXX_STR("[") + getName() + LOG4CXX_STR("] AsyncAppender"));
603603
#ifdef _DEBUG
604604
msg += LOG4CXX_STR(" iterationCount ");
605605
StringHelper::toString(iterationCount, p, msg);

src/main/cpp/domconfigurator.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <log4cxx/logstring.h>
1818
#include <log4cxx/xml/domconfigurator.h>
1919
#include <log4cxx/appender.h>
20+
#include <log4cxx/asyncappender.h>
2021
#include <log4cxx/layout.h>
2122
#include <log4cxx/logger.h>
2223
#include <log4cxx/logmanager.h>
@@ -132,6 +133,7 @@ IMPLEMENT_LOG4CXX_OBJECT(DOMConfigurator)
132133
#define ERROR_HANDLER_TAG "errorHandler"
133134
#define REF_ATTR "ref"
134135
#define ADDITIVITY_ATTR "additivity"
136+
#define ASYNCHRONOUS_ATTR "asynchronous"
135137
#define THRESHOLD_ATTR "threshold"
136138
#define STRINGSTREAM_ATTR "stringstream"
137139
#define CONFIG_DEBUG_ATTR "configDebug"
@@ -560,8 +562,18 @@ void DOMConfigurator::parseChildrenOfLoggerElement(
560562
AppenderMap& appenders)
561563
{
562564
PropertySetter propSetter(logger);
563-
std::vector<AppenderPtr> newappenders;
565+
auto loggerName = m_priv->repository->getRootLogger() == logger
566+
? LogString(LOG4CXX_STR("root"))
567+
: logger->getName();
568+
AsyncAppenderPtr async;
569+
auto lsAsynchronous = subst(getAttribute(utf8Decoder, loggerElement, ASYNCHRONOUS_ATTR));
570+
if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true))
571+
{
572+
async = std::make_shared<AsyncAppender>();
573+
async->setName(loggerName);
574+
}
564575

576+
std::vector<AppenderPtr> newappenders;
565577
for (apr_xml_elem* currentElement = loggerElement->first_child;
566578
currentElement;
567579
currentElement = currentElement->next)
@@ -572,13 +584,17 @@ void DOMConfigurator::parseChildrenOfLoggerElement(
572584
{
573585
if (auto appender = findAppenderByReference(p, utf8Decoder, currentElement, doc, appenders))
574586
{
587+
if (log4cxx::cast<AsyncAppender>(appender)) // An explicitly configured AsyncAppender?
588+
async.reset(); // Not required
575589
if (LogLog::isDebugEnabled())
576590
{
577591
LogLog::debug(LOG4CXX_STR("Adding ") + Appender::getStaticClass().getName()
578592
+ LOG4CXX_STR(" named [") + appender->getName() + LOG4CXX_STR("]")
579593
+ LOG4CXX_STR(" to logger [") + logger->getName() + LOG4CXX_STR("]"));
580594
}
581595
newappenders.push_back(appender);
596+
if (async)
597+
async->addAppender(appender);
582598
}
583599
}
584600
else if (tagName == LEVEL_TAG)
@@ -594,7 +610,17 @@ void DOMConfigurator::parseChildrenOfLoggerElement(
594610
setParameter(p, utf8Decoder, currentElement, propSetter);
595611
}
596612
}
597-
if (newappenders.empty())
613+
if (async && !newappenders.empty())
614+
{
615+
if (LogLog::isDebugEnabled())
616+
{
617+
LogLog::debug(LOG4CXX_STR("Asynchronous logging for [")
618+
+ logger->getName() + LOG4CXX_STR("] is on"));
619+
}
620+
logger->replaceAppenders({async});
621+
m_priv->appenderAdded = true;
622+
}
623+
else if (newappenders.empty())
598624
logger->removeAllAppenders();
599625
else
600626
{

src/main/cpp/propertyconfigurator.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <log4cxx/logstring.h>
1919
#include <log4cxx/propertyconfigurator.h>
20+
#include <log4cxx/asyncappender.h>
2021
#include <log4cxx/helpers/properties.h>
2122
#include <log4cxx/helpers/loglog.h>
2223
#include <log4cxx/helpers/exception.h>
@@ -413,13 +414,18 @@ void PropertyConfigurator::parseLogger(
413414

414415
}
415416

416-
AppenderPtr appender;
417-
LogString appenderName;
418-
std::vector<AppenderPtr> newappenders;
417+
AsyncAppenderPtr async;
418+
auto lsAsynchronous = OptionConverter::findAndSubst(LOG4CXX_STR("log4j.asynchronous.") + loggerName, props);
419+
if (!lsAsynchronous.empty() && OptionConverter::toBoolean(lsAsynchronous, true))
420+
{
421+
async = std::make_shared<AsyncAppender>();
422+
async->setName(loggerName);
423+
}
419424

425+
std::vector<AppenderPtr> newappenders;
420426
while (st.hasMoreTokens())
421427
{
422-
appenderName = StringHelper::trim(st.nextToken());
428+
auto appenderName = StringHelper::trim(st.nextToken());
423429

424430
if (appenderName.empty() || appenderName == LOG4CXX_STR(","))
425431
{
@@ -431,18 +437,30 @@ void PropertyConfigurator::parseLogger(
431437
LogLog::debug(LOG4CXX_STR("Parsing ") + Appender::getStaticClass().getName()
432438
+ LOG4CXX_STR(" named [") + appenderName + LOG4CXX_STR("]"));
433439
}
434-
appender = parseAppender(props, appenderName);
435-
436-
if (appender != 0)
440+
if (auto appender = parseAppender(props, appenderName))
437441
{
438442
newappenders.push_back(appender);
443+
if (log4cxx::cast<AsyncAppender>(appender)) // An explicitly configured AsyncAppender?
444+
async.reset(); // Not required
445+
if (async)
446+
async->addAppender(appender);
439447
}
440448
}
441449
#if 15 < LOG4CXX_ABI_VERSION
442450
if (!newappenders.empty())
443451
m_priv->appenderAdded = true;
444452
#endif
445-
logger->reconfigure( newappenders, additivity );
453+
if (async && !newappenders.empty())
454+
{
455+
if (LogLog::isDebugEnabled())
456+
{
457+
LogLog::debug(LOG4CXX_STR("Asynchronous logging for [")
458+
+ loggerName + LOG4CXX_STR("] is on"));
459+
}
460+
logger->reconfigure( {async}, additivity );
461+
}
462+
else
463+
logger->reconfigure( newappenders, additivity );
446464
}
447465

448466
AppenderPtr PropertyConfigurator::parseAppender(

src/main/include/log4cxx/appenderskeleton.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class LOG4CXX_EXPORT AppenderSkeleton :
9292
9393
Supported options | Supported values | Default value |
9494
-------------- | ---------------- | --------------- |
95+
Name | {any} | - |
9596
Threshold | Trace,Debug,Info,Warn,Error,Fatal,Off,All | All |
9697
*/
9798
void setOption(const LogString& option, const LogString& value) override;

src/main/include/log4cxx/asyncappender.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,21 @@ The AsyncAppender stores the logging event in a bounded buffer
3434
and then returns control to the application.
3535
A separate thread forwards events to the attached appender(s).
3636
37+
An AsyncAppender is used when you configure a logger to be asynchronous.
38+
These AsyncAppender(s) use [the default values](@ref log4cxx::AsyncAppender::setOption) for all options
39+
and they cannot be changed using configuration file entries.
40+
For more control over the AsyncAppender options,
41+
use <b>appender-ref</b> element in the logger configuration instead.
42+
3743
<b>Important notes:</b>
3844
- Your application must call LogManager::shutdown when it exits
3945
to prevent undefined behaviour when using this appender.
40-
- Runtime configuration requires an XML configuration file
46+
- Runtime configuration of options requires an XML configuration file
4147
(see the example below).
4248
4349
This appender is useful when outputting to a slow event sink,
44-
for example, a remote SMTP server or a database.
50+
for example, unbuffered output to a file,
51+
a remote SMTP server or a database.
4552
Note that configuring a FileAppender to use [buffered output](@ref log4cxx::FileAppender::setOption)
4653
usually results in lower overhead than
4754
attaching the FileAppender to an AsyncAppender

src/site/markdown/change-report-gh.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ and the LOG4CXX_CONFIGURATION environment variable (see log4cxx::spi::Configurat
6363
\[[#529](https://github.com/apache/logging-log4cxx/pull/529)\]
6464
* New logging macros that defer binary-to-text conversion until used in AsyncAppender's background thread
6565
\[[#548](https://github.com/apache/logging-log4cxx/pull/548)\]
66+
* A simplified way to attach an AsyncAppender to a logger using a configuration file
67+
\[[#550](https://github.com/apache/logging-log4cxx/pull/550)\]
6668

6769
The following issues have been addressed:
6870

src/site/markdown/configuration-samples.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,21 @@ The variable names are [documented here](@ref log4cxx.spi.Configurator.propertie
8686
To check the correct values are used when your configuration file is loaded,
8787
use [Log4cxx internal debugging].
8888

89+
# Configuring a logger to use asynchronous output
90+
91+
Log4cxx 1.6 allows you to more easily attach an [AsyncAppender](@ref log4cxx.AsyncAppender)
92+
to a logger using a configuration file.
93+
- If using a properties file, add the line <code>log4j.asynchronous.{LOGGER_NAME}=true</code> to your file
94+
- If using an XML file, add the <code>asynchronous="true"</code> attribute in the <code>\<logger></code> element.
95+
96+
The "asynchronous" attribute is only relevent for a logger with attached appenders.
97+
The attribute is ignored if the logger does not have any directly attached appenders.
98+
99+
The "asynchronous" attribute results in the configured appender(s)
100+
being attached an [AsyncAppender](@ref log4cxx.AsyncAppender)
101+
and it is the [AsyncAppender](@ref log4cxx.AsyncAppender)
102+
that is attached directly to the logger.
103+
89104
# Configuration Samples {#configuration-samples}
90105

91106
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:
124139
~~~{.properties}
125140
# Uncomment a line to enable debugging for a category
126141
log4j.rootCategory=INFO, A1
142+
log4j.asynchronous.root=true
127143
128144
log4j.appender.A1=org.apache.log4j.RollingFileAppender
129145
log4j.appender.A1.MaxFileSize=5MB
@@ -286,7 +302,7 @@ to store a log file per executable in a product related logs directory:
286302
</layout>
287303
</appender>
288304
289-
<root>
305+
<root asynchronous="true" >
290306
<priority value="info" />
291307
<appender-ref ref="ConsoleAppender"/>
292308
<appender-ref ref="FileAppender"/>

src/test/cpp/asyncappendertestcase.cpp

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <log4cxx/helpers/stringhelper.h>
3333
#include <log4cxx/spi/location/locationinfo.h>
3434
#include <log4cxx/xml/domconfigurator.h>
35+
#include <log4cxx/propertyconfigurator.h>
3536
#include <log4cxx/file.h>
3637
#include <thread>
3738

@@ -138,8 +139,10 @@ class AsyncAppenderTestCase : public AppenderSkeletonTestCase
138139
LOGUNIT_TEST(testBufferOverflowBehavior);
139140
LOGUNIT_TEST(testLoggingAppender);
140141
#if LOG4CXX_HAS_DOMCONFIGURATOR
141-
LOGUNIT_TEST(testConfiguration);
142+
LOGUNIT_TEST(testXMLConfiguration);
143+
LOGUNIT_TEST(testAsyncLoggerXML);
142144
#endif
145+
LOGUNIT_TEST(testAsyncLoggerProperties);
143146
LOGUNIT_TEST_SUITE_END();
144147

145148
#ifdef _DEBUG
@@ -531,7 +534,7 @@ class AsyncAppenderTestCase : public AppenderSkeletonTestCase
531534
}
532535

533536
#if LOG4CXX_HAS_DOMCONFIGURATOR
534-
void testConfiguration()
537+
void testXMLConfiguration()
535538
{
536539
// Configure Log4cxx
537540
auto status = xml::DOMConfigurator::configure("input/xml/asyncAppender1.xml");
@@ -562,8 +565,66 @@ class AsyncAppenderTestCase : public AppenderSkeletonTestCase
562565
LOGUNIT_ASSERT_EQUAL(LEN, v.size());
563566
LOGUNIT_ASSERT(vectorAppender->isClosed());
564567
}
568+
569+
void testAsyncLoggerXML()
570+
{
571+
// Configure Log4cxx
572+
auto status = xml::DOMConfigurator::configure("input/xml/asyncLogger.xml");
573+
LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
574+
575+
// Check configuration is as expected
576+
auto root = Logger::getRootLogger();
577+
auto appenders = root->getAllAppenders();
578+
LOGUNIT_ASSERT_EQUAL(1, int(appenders.size()));
579+
auto asyncAppender = log4cxx::cast<AsyncAppender>(appenders.front());
580+
LOGUNIT_ASSERT(asyncAppender);
581+
582+
// Log some messages
583+
size_t LEN = 20;
584+
for (size_t i = 0; i < LEN; i++)
585+
{
586+
LOG4CXX_INFO_ASYNC(root, "message" << i);
587+
}
588+
asyncAppender->close();
589+
590+
// Check all message were received
591+
auto vectorAppender = log4cxx::cast<VectorAppender>(asyncAppender->getAppender(LOG4CXX_STR("VECTOR")));
592+
LOGUNIT_ASSERT(vectorAppender);
593+
auto& v = vectorAppender->getVector();
594+
LOGUNIT_ASSERT_EQUAL(LEN, v.size());
595+
LOGUNIT_ASSERT(vectorAppender->isClosed());
596+
}
565597
#endif
566598

599+
void testAsyncLoggerProperties()
600+
{
601+
// Configure Log4cxx
602+
auto status = PropertyConfigurator::configure("input/asyncLogger.properties");
603+
LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
604+
605+
// Check configuration is as expected
606+
auto root = Logger::getRootLogger();
607+
auto appenders = root->getAllAppenders();
608+
LOGUNIT_ASSERT_EQUAL(1, int(appenders.size()));
609+
auto asyncAppender = log4cxx::cast<AsyncAppender>(appenders.front());
610+
LOGUNIT_ASSERT(asyncAppender);
611+
612+
// Log some messages
613+
size_t LEN = 20;
614+
for (size_t i = 0; i < LEN; i++)
615+
{
616+
LOG4CXX_INFO_ASYNC(root, "message" << i);
617+
}
618+
asyncAppender->close();
619+
620+
// Check all message were received
621+
auto vectorAppender = log4cxx::cast<VectorAppender>(asyncAppender->getAppender(LOG4CXX_STR("VECTOR")));
622+
LOGUNIT_ASSERT(vectorAppender);
623+
auto& v = vectorAppender->getVector();
624+
LOGUNIT_ASSERT_EQUAL(LEN, v.size());
625+
LOGUNIT_ASSERT(vectorAppender->isClosed());
626+
}
627+
567628

568629
};
569630

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
log4j.rootCategory=INFO, A1
17+
log4j.asynchronous.root=true
18+
19+
log4j.appender.A1=org.apache.log4j.VectorAppender
20+
log4j.appender.A1.name=VECTOR

0 commit comments

Comments
 (0)