| /* | |
| * 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. | |
| */ | |
| #define NOMINMAX | |
| #include "logunit.h" | |
| #include <log4cxx/loggerinstance.h> | |
| #include <log4cxx/logmanager.h> | |
| #include <log4cxx/simplelayout.h> | |
| #include "vectorappender.h" | |
| #include <log4cxx/asyncappender.h> | |
| #include "appenderskeletontestcase.h" | |
| #include <log4cxx/helpers/loglog.h> | |
| #include <log4cxx/helpers/pool.h> | |
| #include <log4cxx/helpers/transcoder.h> | |
| #include <log4cxx/varia/fallbackerrorhandler.h> | |
| #include <apr_strings.h> | |
| #include "testchar.h" | |
| #include <log4cxx/helpers/stringhelper.h> | |
| #include <log4cxx/spi/location/locationinfo.h> | |
| #include <log4cxx/xml/domconfigurator.h> | |
| #include <log4cxx/propertyconfigurator.h> | |
| #include <log4cxx/fileappender.h> | |
| #include <log4cxx/file.h> | |
| #include <ostream> | |
| #include <thread> | |
| #include <atomic> | |
| #include <fstream> | |
| #if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT | |
| #include <fmt/core.h> | |
| #include <fmt/ostream.h> | |
| #if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR | |
| #include <fmt/xchar.h> | |
| #endif | |
| struct MyStruct | |
| { | |
| int value; | |
| }; | |
| using OutputStreamType = std::basic_ostream<LOG4CXX_NS::logchar>; | |
| OutputStreamType& operator<<(OutputStreamType& stream, const MyStruct& data) | |
| { | |
| stream << LOG4CXX_STR("[MyStruct value: ") << data.value << LOG4CXX_STR("]"); | |
| return stream; | |
| } | |
| #if FMT_VERSION >= (9 * 10000) | |
| template <> struct fmt::formatter<MyStruct, LOG4CXX_NS::logchar> : fmt::basic_ostream_formatter<LOG4CXX_NS::logchar> {}; | |
| #endif | |
| #endif | |
| using namespace LOG4CXX_NS; | |
| using namespace LOG4CXX_NS::helpers; | |
| using namespace LOG4CXX_NS::spi; | |
| class NullPointerAppender : public AppenderSkeleton | |
| { | |
| public: | |
| NullPointerAppender() | |
| { | |
| } | |
| /** | |
| * @{inheritDoc} | |
| */ | |
| void append( const spi::LoggingEventPtr& event ) override | |
| { | |
| throw RuntimeException(LOG4CXX_STR("Intentional Exception")); | |
| } | |
| void close() override | |
| { | |
| } | |
| bool requiresLayout() const override | |
| { | |
| return false; | |
| } | |
| }; | |
| /** | |
| * Vector appender that can be explicitly blocked. | |
| */ | |
| class BlockableVectorAppender : public VectorAppender | |
| { | |
| private: | |
| std::mutex blocker; | |
| public: | |
| /** | |
| * Create new instance. | |
| */ | |
| BlockableVectorAppender() | |
| { | |
| } | |
| /** | |
| * {@inheritDoc} | |
| */ | |
| void append( const spi::LoggingEventPtr& event ) override | |
| { | |
| std::lock_guard<std::mutex> lock( blocker ); | |
| VectorAppender::append( event ); | |
| } | |
| std::mutex& getBlocker() | |
| { | |
| return blocker; | |
| } | |
| }; | |
| LOG4CXX_PTR_DEF(BlockableVectorAppender); | |
| /** | |
| * Vector appender that throws on the first \c failureCount calls to append. | |
| */ | |
| class TransientlyFailingVectorAppender : public VectorAppender | |
| { | |
| private: | |
| std::atomic<int> failuresRemaining; | |
| public: | |
| TransientlyFailingVectorAppender(int failureCount) | |
| : failuresRemaining(failureCount) | |
| { | |
| } | |
| void append( const spi::LoggingEventPtr& event ) override | |
| { | |
| if (0 < failuresRemaining--) | |
| { | |
| throw RuntimeException(LOG4CXX_STR("Intentional transient exception")); | |
| } | |
| VectorAppender::append( event ); | |
| } | |
| /** Goes negative once an append call has succeeded. */ | |
| int remainingFailures() const | |
| { | |
| return failuresRemaining; | |
| } | |
| }; | |
| LOG4CXX_PTR_DEF(TransientlyFailingVectorAppender); | |
| /** | |
| * An appender that adds logging events | |
| */ | |
| class LoggingVectorAppender : public VectorAppender | |
| { | |
| LoggerInstancePtr logger{ "LoggingVectorAppender" }; | |
| void append( const spi::LoggingEventPtr& event ) override | |
| { | |
| auto& lsMsg = event->getRenderedMessage(); | |
| VectorAppender::append( event ); | |
| if (LogString::npos != lsMsg.find(LOG4CXX_STR("World"))) | |
| { | |
| LOG4CXX_LOGLS(logger, Level::getError(), LOG4CXX_STR("Some appender error")); | |
| LOG4CXX_LOGLS(logger, Level::getWarn(), LOG4CXX_STR("Some appender warning")); | |
| LOG4CXX_LOGLS(logger, Level::getInfo(), LOG4CXX_STR("Some appender information")); | |
| LOG4CXX_LOGLS(logger, Level::getDebug(), LOG4CXX_STR("Some appender detailed data")); | |
| } | |
| } | |
| }; | |
| LOG4CXX_PTR_DEF(LoggingVectorAppender); | |
| /** | |
| * Tests of AsyncAppender. | |
| */ | |
| class AsyncAppenderTestCase : public AppenderSkeletonTestCase | |
| { | |
| LOGUNIT_TEST_SUITE(AsyncAppenderTestCase); | |
| // | |
| // tests inherited from AppenderSkeletonTestCase | |
| // | |
| LOGUNIT_TEST(testDefaultThreshold); | |
| LOGUNIT_TEST(testSetOptionThreshold); | |
| LOGUNIT_TEST(closeTest); | |
| LOGUNIT_TEST(testAutoMessageBufferSelection); | |
| LOGUNIT_TEST(testEventFlush); | |
| LOGUNIT_TEST(testMultiThread); | |
| LOGUNIT_TEST(testBadAppender); | |
| LOGUNIT_TEST(testDispatcherRecoversFromAppenderExceptions); | |
| LOGUNIT_TEST(testBufferOverflowBehavior); | |
| LOGUNIT_TEST(testLoggingAppender); | |
| #if LOG4CXX_HAS_DOMCONFIGURATOR | |
| LOGUNIT_TEST(testXMLConfiguration); | |
| LOGUNIT_TEST(testAsyncLoggerXML); | |
| LOGUNIT_TEST(testRecursiveConfiguration); | |
| #endif | |
| #if ENABLE_FAILING_APPENDER_SIMULATION_TESTING | |
| LOGUNIT_TEST(testAsyncAppenderFallback); | |
| #endif | |
| LOGUNIT_TEST(testAsyncLoggerProperties); | |
| #if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT | |
| LOGUNIT_TEST(testAsyncFmtLogging); | |
| #endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT | |
| LOGUNIT_TEST_SUITE_END(); | |
| #ifdef _DEBUG | |
| struct Fixture | |
| { | |
| Fixture() { | |
| helpers::LogLog::setInternalDebugging(true); | |
| } | |
| } suiteFixture; | |
| #endif | |
| public: | |
| void setUp() override | |
| { | |
| AppenderSkeletonTestCase::setUp(); | |
| } | |
| void tearDown() override | |
| { | |
| LogManager::shutdown(); | |
| AppenderSkeletonTestCase::tearDown(); | |
| } | |
| AppenderSkeleton* createAppenderSkeleton() const override | |
| { | |
| return new AsyncAppender(); | |
| } | |
| // Check it is not possible to write to a closed AsyncAppender | |
| void closeTest() | |
| { | |
| AsyncAppenderPtr asyncAppender; | |
| auto r = LogManager::getLoggerRepository(); | |
| r->ensureIsConfigured([r, &asyncAppender]() | |
| { | |
| asyncAppender = std::make_shared<AsyncAppender>(); | |
| asyncAppender->setName(LOG4CXX_STR("async-CloseTest")); | |
| r->getRootLogger()->addAppender(asyncAppender); | |
| r->setConfigured(true); | |
| }); | |
| LOGUNIT_ASSERT(asyncAppender); | |
| auto vectorAppender = std::make_shared<VectorAppender>(); | |
| asyncAppender->addAppender(vectorAppender); | |
| auto root = r->getRootLogger(); | |
| root->debug(LOG4CXX_TEST_STR("m1")); | |
| asyncAppender->close(); | |
| root->debug(LOG4CXX_TEST_STR("m2")); | |
| // Check one message was received | |
| auto& v = vectorAppender->getVector(); | |
| LOGUNIT_ASSERT_EQUAL(1, int(v.size())); | |
| // Check appender embedded within an AsyncAppender is also closed | |
| LOGUNIT_ASSERT(vectorAppender->isClosed()); | |
| } | |
| // Test behaviour when logging with a char type that is not logchar | |
| void testAutoMessageBufferSelection() | |
| { | |
| // Configure Log4cxx | |
| VectorAppenderPtr vectorAppender; | |
| auto r = LogManager::getLoggerRepository(); | |
| r->ensureIsConfigured([r, &vectorAppender]() | |
| { | |
| vectorAppender = std::make_shared<VectorAppender>(); | |
| r->getRootLogger()->addAppender(vectorAppender); | |
| r->setConfigured(true); | |
| }); | |
| LogString logStr = LOG4CXX_STR("Some logchar string "); | |
| #if LOG4CXX_LOGCHAR_IS_UTF8 | |
| std::wstring otherStr(L"Some non-logchar string "); | |
| #else // !LOG4CXX_LOGCHAR_IS_UTF8 | |
| std::string otherStr("Some non-logchar string "); | |
| #endif // !LOG4CXX_LOGCHAR_IS_UTF8 | |
| // Log some messages | |
| auto root = r->getRootLogger(); | |
| LOG4CXX_INFO(root, logStr << 42); | |
| LOG4CXX_INFO_ASYNC(root, logStr << 42); | |
| int expectedEventCount = 2; | |
| #if !LOG4CXX_LOGCHAR_IS_UTF8 || LOG4CXX_WCHAR_T_API | |
| LOG4CXX_INFO(root, otherStr << 42); | |
| ++expectedEventCount; | |
| #if LOG4CXX_CONCEPTS | |
| LOG4CXX_INFO_ASYNC(root, otherStr << 42); | |
| ++expectedEventCount; | |
| #endif // LOG4CXX_CONCEPTS | |
| #endif // !LOG4CXX_LOGCHAR_IS_UTF8 || LOG4CXX_WCHAR_T_API | |
| // Check all messages were received | |
| auto& v = vectorAppender->getVector(); | |
| for (auto& pEvent : v) | |
| LogLog::debug(pEvent->getRenderedMessage()); | |
| LOGUNIT_ASSERT_EQUAL(expectedEventCount, int(v.size())); | |
| LOGUNIT_ASSERT_EQUAL(v[0]->getRenderedMessage(), v[1]->getRenderedMessage()); | |
| if (4 <= expectedEventCount) | |
| LOGUNIT_ASSERT_EQUAL(v[2]->getRenderedMessage(), v[3]->getRenderedMessage()); | |
| #ifdef GENERATE_THE_STATIC_ASSERT_COMPILATION_ERROR | |
| struct AStruct | |
| { | |
| int value; | |
| } data{ 43 }; | |
| LOG4CXX_INFO_ASYNC(root, "data.value=" << data); | |
| #endif | |
| } | |
| // this test checks all messages are delivered when an AsyncAppender is closed | |
| void testEventFlush() | |
| { | |
| // Configure Log4cxx | |
| auto r = LogManager::getLoggerRepository(); | |
| AsyncAppenderPtr asyncAppender; | |
| r->ensureIsConfigured([r, &asyncAppender]() | |
| { | |
| asyncAppender = std::make_shared<AsyncAppender>(); | |
| asyncAppender->setName(LOG4CXX_STR("async-testEventFlush")); | |
| r->getRootLogger()->addAppender(asyncAppender); | |
| r->setConfigured(true); | |
| }); | |
| LOGUNIT_ASSERT(asyncAppender); | |
| auto vectorAppender = std::make_shared<VectorAppender>(); | |
| vectorAppender->setMillisecondDelay(1); | |
| asyncAppender->addAppender(vectorAppender); | |
| // Log some messages | |
| auto root = r->getRootLogger(); | |
| size_t LEN = 200; // Larger than default buffer size (128) | |
| for (size_t i = 0; i < LEN; i++) | |
| { | |
| LOG4CXX_DEBUG_ASYNC(root, "message" << i); | |
| } | |
| asyncAppender->close(); | |
| root->debug(LOG4CXX_STR("m2")); | |
| const std::vector<spi::LoggingEventPtr>& v = vectorAppender->getVector(); | |
| LOGUNIT_ASSERT_EQUAL(LEN, v.size()); | |
| int i{ 0 }; | |
| for (auto e : v) | |
| { | |
| LogString m(LOG4CXX_STR("message")); | |
| StringHelper::toString(i, m); | |
| LOGUNIT_ASSERT_EQUAL(m, e->getRenderedMessage()); | |
| ++i; | |
| } | |
| LOGUNIT_ASSERT_EQUAL(true, vectorAppender->isClosed()); | |
| } | |
| // this test checks all messages are delivered from multiple threads | |
| void testMultiThread() | |
| { | |
| // Configure Log4cxx | |
| AsyncAppenderPtr asyncAppender; | |
| auto r = LogManager::getLoggerRepository(); | |
| r->ensureIsConfigured([r, &asyncAppender]() | |
| { | |
| asyncAppender = std::make_shared<AsyncAppender>(); | |
| asyncAppender->setName(LOG4CXX_STR("async-testMultiThread")); | |
| r->getRootLogger()->addAppender(asyncAppender); | |
| r->setConfigured(true); | |
| }); | |
| LOGUNIT_ASSERT(asyncAppender); | |
| auto vectorAppender = std::make_shared<VectorAppender>(); | |
| asyncAppender->addAppender(vectorAppender); | |
| // Log some messages | |
| int LEN = 2000; // Larger than default buffer size (128) | |
| auto threadCount = std::max(static_cast<int>(std::thread::hardware_concurrency() - 1), 2); | |
| auto root = r->getRootLogger(); | |
| std::vector<std::thread> threads; | |
| for ( int x = 0; x < threadCount; x++ ) | |
| { | |
| threads.emplace_back([root, LEN]() | |
| { | |
| for (int i = 0; i < LEN; i++) | |
| { | |
| LOG4CXX_DEBUG_ASYNC(root, "message" << i); | |
| } | |
| }); | |
| } | |
| for ( auto& thr : threads ) | |
| { | |
| if ( thr.joinable() ) | |
| { | |
| thr.join(); | |
| } | |
| } | |
| asyncAppender->close(); | |
| // Check all message were received | |
| auto& v = vectorAppender->getVector(); | |
| LOGUNIT_ASSERT_EQUAL(LEN*threadCount, (int)v.size()); | |
| std::map<LogString, int> perThreadCount; | |
| std::vector<int> msgCount(LEN, 0); | |
| for (auto m : v) | |
| { | |
| auto i = StringHelper::toInt(m->getRenderedMessage().substr(7)); | |
| LOGUNIT_ASSERT(0 <= i); | |
| LOGUNIT_ASSERT(i < LEN); | |
| ++msgCount[i]; | |
| ++perThreadCount[m->getThreadName()]; | |
| } | |
| LOGUNIT_ASSERT_EQUAL(threadCount, (int)perThreadCount.size()); | |
| for (auto& item : perThreadCount) | |
| { | |
| LOGUNIT_ASSERT_EQUAL(item.second, LEN); | |
| } | |
| for (size_t i = 0; i < LEN; i++) | |
| LOGUNIT_ASSERT_EQUAL(msgCount[i], threadCount); | |
| } | |
| /** | |
| * Checks that async will switch a bad appender to another appender. | |
| */ | |
| void testBadAppender() | |
| { | |
| // Configure Log4cxx | |
| AsyncAppenderPtr asyncAppender; | |
| auto r = LogManager::getLoggerRepository(); | |
| r->ensureIsConfigured([r, &asyncAppender]() | |
| { | |
| asyncAppender = std::make_shared<AsyncAppender>(); | |
| asyncAppender->setName(LOG4CXX_STR("async-testBadAppender")); | |
| asyncAppender->addAppender(std::make_shared<NullPointerAppender>()); | |
| asyncAppender->activateOptions(); | |
| r->getRootLogger()->addAppender(asyncAppender); | |
| r->setConfigured(true); | |
| }); | |
| LOGUNIT_ASSERT(asyncAppender); | |
| auto vectorAppender = std::make_shared<VectorAppender>(); | |
| vectorAppender->setName(LOG4CXX_STR("async-memoryAppender")); | |
| auto root = r->getRootLogger(); | |
| auto errorHandler = std::make_shared<varia::FallbackErrorHandler>(); | |
| errorHandler->setAppender(asyncAppender); | |
| errorHandler->setBackupAppender(vectorAppender); | |
| errorHandler->setLogger(root); | |
| asyncAppender->setErrorHandler(errorHandler); | |
| // Log some messages | |
| LOG4CXX_INFO(root, "Message"); | |
| std::this_thread::sleep_for( std::chrono::milliseconds( 30 ) ); | |
| LOGUNIT_ASSERT(errorHandler->errorReported()); | |
| LOG4CXX_INFO(root, "Message"); | |
| // Check a message was received | |
| auto& v = vectorAppender->getVector(); | |
| LOGUNIT_ASSERT(0 < v.size()); | |
| } | |
| /** | |
| * Checks the dispatch thread survives exceptions thrown by an | |
| * attached appender. Regression test: two exceptions at any two | |
| * points in the dispatch thread's lifetime used to stop dispatching | |
| * permanently, after which producers using the default Blocking=true | |
| * hung forever once the buffer filled. | |
| */ | |
| void testDispatcherRecoversFromAppenderExceptions() | |
| { | |
| // Configure Log4cxx | |
| AsyncAppenderPtr async; | |
| auto r = LogManager::getLoggerRepository(); | |
| r->ensureIsConfigured([r, &async]() | |
| { | |
| async = std::make_shared<AsyncAppender>(); | |
| async->setName(LOG4CXX_STR("async-testDispatcherRecovers")); | |
| async->activateOptions(); | |
| r->getRootLogger()->addAppender(async); | |
| r->setConfigured(true); | |
| }); | |
| LOGUNIT_ASSERT(async); | |
| // The old code tolerated only 4 exceptions over the appender lifetime | |
| // (2 per dispatch thread incarnation, one restart allowed) | |
| auto failingAppender = std::make_shared<TransientlyFailingVectorAppender>(4); | |
| failingAppender->setName(LOG4CXX_STR("async-transientlyFailingVector")); | |
| async->addAppender(failingAppender); | |
| // Log messages until one has been delivered (bounded wait) | |
| auto root = r->getRootLogger(); | |
| for (int i = 0; 0 <= failingAppender->remainingFailures() && i < 100; i++) | |
| { | |
| LOG4CXX_INFO_ASYNC(root, "message" << i); | |
| std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); | |
| } | |
| LOG4CXX_INFO_ASYNC(root, "final message"); | |
| async->close(); | |
| // Check dispatching recovered once the transient fault cleared | |
| auto& v = failingAppender->getVector(); | |
| LOGUNIT_ASSERT(!v.empty()); | |
| LOGUNIT_ASSERT(v.back()->getRenderedMessage() == LOG4CXX_STR("final message")); | |
| } | |
| /** | |
| * Tests behavior when the the async buffer overflows. | |
| */ | |
| void testBufferOverflowBehavior() | |
| { | |
| // Configure Log4cxx | |
| AsyncAppenderPtr async; | |
| auto r = LogManager::getLoggerRepository(); | |
| r->ensureIsConfigured([r, &async]() | |
| { | |
| async = std::make_shared<AsyncAppender>(); | |
| async->setName(LOG4CXX_STR("async-testBufferOverflowBehavior")); | |
| async->setBufferSize(5); | |
| async->setBlocking(false); | |
| async->activateOptions(); | |
| r->getRootLogger()->addAppender(async); | |
| r->setConfigured(true); | |
| }); | |
| LOGUNIT_ASSERT(async); | |
| auto blockableAppender = std::make_shared<BlockableVectorAppender>(); | |
| blockableAppender->setName(LOG4CXX_STR("async-blockableVector")); | |
| async->addAppender(blockableAppender); | |
| // Log some messages | |
| auto rootLogger = r->getRootLogger(); | |
| LOG4CXX_INFO_ASYNC(rootLogger, "Hello, World"); // This causes the dispatch thread creation | |
| std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); // Wait for the dispatch thread to be ready | |
| { | |
| std::lock_guard<std::mutex> sync(blockableAppender->getBlocker()); | |
| for (int i = 0; i < 140; i++) | |
| { | |
| LOG4CXX_INFO_ASYNC(rootLogger, "Hello, World " << i); | |
| } | |
| LOG4CXX_ERROR_ASYNC(rootLogger, "That's all folks."); | |
| } | |
| async->close(); | |
| // Check which messages were output | |
| auto& events = blockableAppender->getVector(); | |
| LOGUNIT_ASSERT(!events.empty()); | |
| LOGUNIT_ASSERT(events.size() <= 142); | |
| LoggingEventPtr initialEvent = events.front(); | |
| LOGUNIT_ASSERT(initialEvent->getRenderedMessage() == LOG4CXX_STR("Hello, World")); | |
| std::map<LevelPtr, int> levelCount; | |
| int discardMessageCount{ 0 }; | |
| LoggingEventPtr discardEvent; | |
| for (auto& e : events) | |
| { | |
| auto message = e->getRenderedMessage(); | |
| LogLog::debug(message); | |
| ++levelCount[e->getLevel()]; | |
| if (message.substr(0, 10) == LOG4CXX_STR("Discarded ")) | |
| { | |
| ++discardMessageCount; | |
| discardEvent = e; | |
| } | |
| } | |
| if (helpers::LogLog::isDebugEnabled()) | |
| { | |
| LogString msg{ LOG4CXX_STR("messageCounts:") }; | |
| for (auto& item : levelCount) | |
| { | |
| msg += LOG4CXX_STR(" "); | |
| msg += item.first->toString(); | |
| msg += LOG4CXX_STR(" "); | |
| StringHelper::toString(item.second, msg); | |
| } | |
| msg += LOG4CXX_STR(" "); | |
| msg += LOG4CXX_STR("Discarded "); | |
| StringHelper::toString(discardMessageCount, msg); | |
| helpers::LogLog::debug(msg); | |
| } | |
| // Check this test has activated the discard logic | |
| LOGUNIT_ASSERT(1 <= discardMessageCount); | |
| LOGUNIT_ASSERT(5 < levelCount[Level::getInfo()]); | |
| // Check the discard message is the logging event of the highest level | |
| LOGUNIT_ASSERT_EQUAL(levelCount[Level::getError()], 1); | |
| LOGUNIT_ASSERT_EQUAL(discardEvent->getLevel(), Level::getError()); | |
| // Check the discard message does not have location info | |
| LOGUNIT_ASSERT_EQUAL(LOG4CXX_NS::spi::LocationInfo::getLocationUnavailable().getClassName(), | |
| discardEvent->getLocationInformation().getClassName()); | |
| } | |
| /** | |
| * Tests behavior when the appender attached to a AsyncAppender adds logging events | |
| */ | |
| void testLoggingAppender() | |
| { | |
| // Configure Log4cxx | |
| AsyncAppenderPtr async; | |
| auto r = LogManager::getLoggerRepository(); | |
| r->ensureIsConfigured([r, &async]() | |
| { | |
| async = std::make_shared<AsyncAppender>(); | |
| async->setName(LOG4CXX_STR("withLoggingAppender")); | |
| async->setBufferSize(5); | |
| async->activateOptions(); | |
| r->getRootLogger()->addAppender(async); | |
| r->setConfigured(true); | |
| }); | |
| LOGUNIT_ASSERT(async); | |
| auto loggingAppender = std::make_shared<LoggingVectorAppender>(); | |
| loggingAppender->setName(LOG4CXX_STR("loggingAppender")); | |
| async->addAppender(loggingAppender); | |
| // Log some messages | |
| auto rootLogger = r->getRootLogger(); | |
| LOG4CXX_INFO_ASYNC(rootLogger, "Hello, World"); // This causes the dispatch thread creation | |
| std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); // Wait for the dispatch thread to be ready | |
| for (int i = 0; i < 10; i++) | |
| { | |
| LOG4CXX_INFO_ASYNC(rootLogger, "Hello, World " << i); | |
| } | |
| LOG4CXX_INFO_ASYNC(rootLogger, "Bye bye World"); | |
| std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); // Wait for the dispatch thread take the above events | |
| async->close(); | |
| // Check which messages were received | |
| auto& events = loggingAppender->getVector(); | |
| std::map<LevelPtr, int> levelCount; | |
| int discardMessageCount{ 0 }; | |
| int eventCount[] = { 0, 0 }; | |
| for (auto& e : events) | |
| { | |
| ++levelCount[e->getLevel()]; | |
| auto message = e->getRenderedMessage(); | |
| LogLog::debug(message); | |
| auto isAppenderMessage = (message.npos == message.find(LOG4CXX_STR("World"))); | |
| ++eventCount[isAppenderMessage]; | |
| if (message.substr(0, 10) == LOG4CXX_STR("Discarded ")) | |
| { | |
| ++discardMessageCount; | |
| LOGUNIT_ASSERT(isAppenderMessage); | |
| } | |
| } | |
| if (helpers::LogLog::isDebugEnabled()) | |
| { | |
| LogString msg{ LOG4CXX_STR("messageCounts:") }; | |
| msg += LOG4CXX_STR(" nonAppender "); | |
| StringHelper::toString(eventCount[0], msg); | |
| msg += LOG4CXX_STR(" appender "); | |
| StringHelper::toString(eventCount[1], msg); | |
| msg += LOG4CXX_STR(" discard "); | |
| StringHelper::toString(discardMessageCount, msg); | |
| LogLog::debug(msg); | |
| } | |
| LOGUNIT_ASSERT(12 < events.size()); | |
| LOGUNIT_ASSERT_EQUAL(12, eventCount[0]); | |
| } | |
| #if LOG4CXX_HAS_DOMCONFIGURATOR | |
| void testXMLConfiguration() | |
| { | |
| // Configure Log4cxx | |
| auto status = xml::DOMConfigurator::configure("input/xml/asyncAppender1.xml"); | |
| LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured); | |
| // Check configuration is as expected | |
| auto root = Logger::getRootLogger(); | |
| auto asyncAppender = LOG4CXX_NS::cast<AsyncAppender>(root->getAppender(LOG4CXX_STR("ASYNC"))); | |
| LOGUNIT_ASSERT(asyncAppender); | |
| LOGUNIT_ASSERT_EQUAL(100, asyncAppender->getBufferSize()); | |
| LOGUNIT_ASSERT_EQUAL(false, asyncAppender->getBlocking()); | |
| auto nestedAppenders = asyncAppender->getAllAppenders(); | |
| LOGUNIT_ASSERT_EQUAL(1, int(nestedAppenders.size())); | |
| // Log some messages | |
| size_t LEN = 20; | |
| for (size_t i = 0; i < LEN; i++) | |
| { | |
| LOG4CXX_DEBUG_ASYNC(root, "message" << i); | |
| } | |
| asyncAppender->close(); | |
| // Check all message were received | |
| auto vectorAppender = LOG4CXX_NS::cast<VectorAppender>(asyncAppender->getAppender(LOG4CXX_STR("VECTOR"))); | |
| LOGUNIT_ASSERT(vectorAppender); | |
| auto& v = vectorAppender->getVector(); | |
| 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_NS::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_NS::cast<VectorAppender>(asyncAppender->getAppender(LOG4CXX_STR("VECTOR"))); | |
| LOGUNIT_ASSERT(vectorAppender); | |
| auto& v = vectorAppender->getVector(); | |
| LOGUNIT_ASSERT_EQUAL(LEN, v.size()); | |
| LOGUNIT_ASSERT(vectorAppender->isClosed()); | |
| } | |
| void testRecursiveConfiguration() | |
| { | |
| // Configure Log4cxx | |
| auto status = xml::DOMConfigurator::configure("input/xml/recursiveConfig.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_NS::cast<AsyncAppender>(appenders.front()); | |
| LOGUNIT_ASSERT(asyncAppender); | |
| VectorAppenderPtr vectorAppender; | |
| for (auto attachedAppender : asyncAppender->getAllAppenders()) | |
| { | |
| if (auto async2 = LOG4CXX_NS::cast<AsyncAppender>(attachedAppender)) | |
| { | |
| for (auto appender : async2->getAllAppenders()) | |
| { | |
| vectorAppender = LOG4CXX_NS::cast<VectorAppender>(appender); | |
| if (vectorAppender) | |
| break; | |
| } | |
| } | |
| } | |
| LOGUNIT_ASSERT(vectorAppender); | |
| // Log some messages | |
| size_t LEN = 20; | |
| for (size_t i = 0; i < LEN; i++) | |
| { | |
| LOG4CXX_INFO_ASYNC(root, "message" << i); | |
| } | |
| std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); | |
| asyncAppender->close(); | |
| // Check all message were received | |
| auto& v = vectorAppender->getVector(); | |
| LOGUNIT_ASSERT_EQUAL(LEN, v.size()); | |
| LOGUNIT_ASSERT(vectorAppender->isClosed()); | |
| } | |
| #endif | |
| #if ENABLE_FAILING_APPENDER_SIMULATION_TESTING | |
| void testAsyncAppenderFallback() | |
| { | |
| // Configure Log4cxx | |
| auto status = xml::DOMConfigurator::configure("input/xml/asyncWithFallback.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_NS::cast<AsyncAppender>(appenders.front()); | |
| LOGUNIT_ASSERT(asyncAppender); | |
| auto asyncAppenders = asyncAppender->getAllAppenders(); | |
| LOGUNIT_ASSERT_EQUAL(1, int(asyncAppenders.size())); | |
| auto primaryAppender = LOG4CXX_NS::cast<FileAppender>(asyncAppenders.front()); | |
| LOGUNIT_ASSERT(primaryAppender); | |
| auto primaryFileName = primaryAppender->getFile(); | |
| LOGUNIT_ASSERT(11 < primaryFileName.size()); | |
| LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("Primary.log"), primaryFileName.substr(primaryFileName.size() - 11)); | |
| // Log some messages | |
| size_t LEN = 2000; | |
| for (size_t i = 0; i < LEN; i++) | |
| { | |
| LOG4CXX_INFO_ASYNC(root, "message " << i); | |
| } | |
| std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); | |
| asyncAppenders = asyncAppender->getAllAppenders(); | |
| LOGUNIT_ASSERT_EQUAL(1, int(asyncAppenders.size())); | |
| auto backupAppender = LOG4CXX_NS::cast<FileAppender>(asyncAppenders.front()); | |
| LOGUNIT_ASSERT(backupAppender); | |
| auto backupFileName = backupAppender->getFile(); | |
| LOGUNIT_ASSERT(10 < backupFileName.size()); | |
| LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("Backup.log"), backupFileName.substr(backupFileName.size() - 10)); | |
| // Check all messages were written | |
| std::vector<int> messageCount; | |
| for (auto const& filePathLS : {primaryFileName, backupFileName}) | |
| { | |
| LOG4CXX_ENCODE_CHAR(filePath, filePathLS); | |
| std::ifstream input(filePath); | |
| for (std::string line; std::getline(input, line);) | |
| { | |
| auto pos = line.rfind(' '); | |
| if (line.npos != pos && pos + 1 < line.size()) | |
| { | |
| try | |
| { | |
| auto msgNumber = std::stoull(line.substr(pos)); | |
| if (messageCount.size() <= msgNumber) | |
| messageCount.resize(msgNumber + 1); | |
| ++messageCount[msgNumber]; | |
| } | |
| catch (std::exception const& ex) | |
| { | |
| LogString msg; | |
| helpers::Transcoder::decode(ex.what(), msg); | |
| msg += LOG4CXX_STR(" processing\n"); | |
| helpers::Transcoder::decode(line, msg); | |
| helpers::LogLog::warn(msg); | |
| } | |
| } | |
| } | |
| } | |
| for (auto& count : messageCount) | |
| LOGUNIT_ASSERT_EQUAL(1, count); | |
| } | |
| #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_NS::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_NS::cast<VectorAppender>(asyncAppender->getAppender(LOG4CXX_STR("VECTOR"))); | |
| LOGUNIT_ASSERT(vectorAppender); | |
| auto& v = vectorAppender->getVector(); | |
| LOGUNIT_ASSERT_EQUAL(LEN, v.size()); | |
| LOGUNIT_ASSERT(vectorAppender->isClosed()); | |
| } | |
| #if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT | |
| /** | |
| * Tests asynchronous logging using FMT | |
| */ | |
| void testAsyncFmtLogging() | |
| { | |
| // Configure Log4cxx | |
| AsyncAppenderPtr async; | |
| auto r = LogManager::getLoggerRepository(); | |
| r->ensureIsConfigured([r, &async]() | |
| { | |
| async = std::make_shared<AsyncAppender>(); | |
| async->setName(LOG4CXX_STR("withAsyncFmtLogging")); | |
| r->getRootLogger()->addAppender(async); | |
| r->setConfigured(true); | |
| }); | |
| LOGUNIT_ASSERT(async); | |
| auto eventStore = std::make_shared<VectorAppender>(); | |
| eventStore->setName(LOG4CXX_STR("VectorAppender")); | |
| async->addAppender(eventStore); | |
| // Log some messages | |
| auto rootLogger = r->getRootLogger(); | |
| LOG4CXX_INFO_FMT_ASYNC(rootLogger, LOG4CXX_STR("Hello, {}"), LOG4CXX_STR("World")); | |
| for (MyStruct i = {0}; i.value < 10; ++i.value) | |
| { | |
| LOG4CXX_INFO_FMT_ASYNC(rootLogger, LOG4CXX_STR("Hello, {}"), i); | |
| } | |
| LOG4CXX_INFO_FMT_ASYNC(rootLogger, "Bye bye {}", "World"); | |
| async->close(); | |
| // Check all parts of all messages were received | |
| auto& events = eventStore->getVector(); | |
| std::vector<int> messageCount; | |
| int eventCount[] = { 0, 0 }; | |
| for (auto& e : events) | |
| { | |
| auto message = e->getRenderedMessage(); | |
| //LogLog::debug(message); | |
| auto isNumberedMessage = (message.npos == message.find(LOG4CXX_STR("World"))); | |
| ++eventCount[isNumberedMessage]; | |
| if (isNumberedMessage) | |
| { | |
| auto pos = message.rfind(' '); | |
| if (message.npos != pos && pos + 1 < message.size()) | |
| { | |
| try | |
| { | |
| auto msgNumber = StringHelper::toInt(message.substr(pos)); | |
| if (messageCount.size() <= msgNumber) | |
| messageCount.resize(msgNumber + 1); | |
| ++messageCount[msgNumber]; | |
| } | |
| catch (std::exception const& ex) | |
| { | |
| LogString msg; | |
| helpers::Transcoder::decode(ex.what(), msg); | |
| msg += LOG4CXX_STR(" processing\n"); | |
| helpers::Transcoder::decode(message, msg); | |
| helpers::LogLog::warn(msg); | |
| } | |
| } | |
| } | |
| } | |
| LOGUNIT_ASSERT_EQUAL(int(messageCount.size()), eventCount[1]); | |
| for (auto& count : messageCount) | |
| LOGUNIT_ASSERT_EQUAL(count, messageCount.front()); | |
| LOGUNIT_ASSERT_EQUAL(2, eventCount[0]); | |
| } | |
| #endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT | |
| }; | |
| LOGUNIT_TEST_SUITE_REGISTRATION(AsyncAppenderTestCase); |