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/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 7c9408586..6983c9be6 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" #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 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(); + async->setName(loggerName); + } + std::vector 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(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 b1c1d939d..9c1fdd24c 100644 --- a/src/main/cpp/propertyconfigurator.cpp +++ b/src/main/cpp/propertyconfigurator.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -413,13 +414,18 @@ void PropertyConfigurator::parseLogger( } - AppenderPtr appender; - LogString appenderName; - std::vector newappenders; + 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()) { - 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(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 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/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/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: 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: - + 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 @@ + + + + + + + + + + +