blob: 42a2eaaee6c0fa76876e1ab7de26a9ad43006a94 [file] [log] [blame]
/*
* 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/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/file.h>
#include <thread>
using namespace log4cxx;
using namespace log4cxx::helpers;
using namespace log4cxx::spi;
class NullPointerAppender : public AppenderSkeleton
{
public:
NullPointerAppender()
{
}
/**
* @{inheritDoc}
*/
void append(const spi::LoggingEventPtr&, log4cxx::helpers::Pool&) override
{
throw NullPointerException(LOG4CXX_STR("Intentional NullPointerException"));
}
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, log4cxx::helpers::Pool& p) override
{
std::lock_guard<std::mutex> lock( blocker );
VectorAppender::append(event, p);
}
std::mutex& getBlocker()
{
return blocker;
}
};
/**
* An appender that adds logging events
*/
class LoggingVectorAppender : public VectorAppender
{
LoggerInstancePtr logger{ "LoggingVectorAppender" };
void append(const spi::LoggingEventPtr& event, log4cxx::helpers::Pool& p) override
{
auto lsMsg = event->getRenderedMessage();
VectorAppender::append(event, p);
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"));
}
}
};
/**
* 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(test2);
LOGUNIT_TEST(testAutoMessageBufferSelection);
LOGUNIT_TEST(testEventFlush);
LOGUNIT_TEST(testMultiThread);
LOGUNIT_TEST(testBadAppender);
LOGUNIT_TEST(testBufferOverflowBehavior);
LOGUNIT_TEST(testLoggingAppender);
#if LOG4CXX_HAS_DOMCONFIGURATOR
LOGUNIT_TEST(testConfiguration);
#endif
LOGUNIT_TEST_SUITE_END();
#ifdef _DEBUG
struct Fixture
{
Fixture() {
helpers::LogLog::setInternalDebugging(true);
}
} suiteFixture;
#endif
public:
void setUp()
{
AppenderSkeletonTestCase::setUp();
}
void tearDown()
{
LogManager::shutdown();
AppenderSkeletonTestCase::tearDown();
}
AppenderSkeleton* createAppenderSkeleton() const
{
return new AsyncAppender();
}
// this test checks whether it is possible to write to a closed AsyncAppender
void closeTest()
{
LoggerPtr root = Logger::getRootLogger();
LayoutPtr layout = LayoutPtr(new SimpleLayout());
VectorAppenderPtr vectorAppender = VectorAppenderPtr(new VectorAppender());
AsyncAppenderPtr asyncAppender = AsyncAppenderPtr(new AsyncAppender());
asyncAppender->setName(LOG4CXX_STR("async-CloseTest"));
asyncAppender->addAppender(vectorAppender);
root->addAppender(asyncAppender);
root->debug(LOG4CXX_TEST_STR("m1"));
asyncAppender->close();
root->debug(LOG4CXX_TEST_STR("m2"));
const std::vector<spi::LoggingEventPtr>& v = vectorAppender->getVector();
LOGUNIT_ASSERT_EQUAL((size_t) 1, v.size());
}
// this test checks whether appenders embedded within an AsyncAppender are also
// closed
void test2()
{
LoggerPtr root = Logger::getRootLogger();
LayoutPtr layout = SimpleLayoutPtr(new SimpleLayout());
VectorAppenderPtr vectorAppender = VectorAppenderPtr(new VectorAppender());
AsyncAppenderPtr asyncAppender = AsyncAppenderPtr(new AsyncAppender());
asyncAppender->setName(LOG4CXX_STR("async-test2"));
asyncAppender->addAppender(vectorAppender);
root->addAppender(asyncAppender);
root->debug(LOG4CXX_TEST_STR("m1"));
asyncAppender->close();
root->debug(LOG4CXX_TEST_STR("m2"));
const std::vector<spi::LoggingEventPtr>& v = vectorAppender->getVector();
LOGUNIT_ASSERT_EQUAL((size_t) 1, v.size());
LOGUNIT_ASSERT(vectorAppender->isClosed());
}
// Test behaviour when logging with a char type that is not logchar
void testAutoMessageBufferSelection()
{
VectorAppenderPtr vectorAppender;
auto r = LogManager::getLoggerRepository();
r->ensureIsConfigured([r, &vectorAppender]()
{
vectorAppender = std::make_shared<VectorAppender>();
r->getRootLogger()->addAppender(vectorAppender);
});
auto root = r->getRootLogger();
int expectedMessageCount = 1;
#ifdef LOG4CXX_XXXX_ASYNC_MACROS_WORK_WITH_ANY_CHAR_TYPE
++expectedMessageCount
#if LOG4CXX_LOGCHAR_IS_UTF8
LOG4CXX_INFO_ASYNC(root, L"Some wide string " << 42);
#else
LOG4CXX_INFO_ASYNC(root, "Some narrow string " << 42);
#endif
#endif // LOG4CXX_XXXX_ASYNC_MACROS_WORK_WITH_ANY_CHAR_TYPE
#if LOG4CXX_LOGCHAR_IS_UTF8
LOG4CXX_INFO(root, L"Some wide string " << 42);
#else
LOG4CXX_INFO(root, "Some narrow string " << 42);
#endif
auto& v = vectorAppender->getVector();
LOGUNIT_ASSERT_EQUAL(expectedMessageCount, int(v.size()));
}
// this test checks all messages are delivered when an AsyncAppender is closed
void testEventFlush()
{
size_t LEN = 200; // Larger than default buffer size (128)
LoggerPtr root = Logger::getRootLogger();
VectorAppenderPtr vectorAppender = VectorAppenderPtr(new VectorAppender());
vectorAppender->setMillisecondDelay(1);
AsyncAppenderPtr asyncAppender = AsyncAppenderPtr(new AsyncAppender());
asyncAppender->setName(LOG4CXX_STR("async-testEventFlush"));
asyncAppender->addAppender(vectorAppender);
root->addAppender(asyncAppender);
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());
Pool p;
for (size_t i = 0; i < LEN; i++)
{
LogString m(LOG4CXX_STR("message"));
StringHelper::toString(i, p, m);
LOGUNIT_ASSERT(v[i]->getRenderedMessage() == m);
}
LOGUNIT_ASSERT_EQUAL(true, vectorAppender->isClosed());
}
// this test checks all messages are delivered from multiple threads
void testMultiThread()
{
int LEN = 2000; // Larger than default buffer size (128)
auto threadCount = std::max(static_cast<int>(std::thread::hardware_concurrency() - 1), 2);
auto root = Logger::getRootLogger();
auto vectorAppender = std::make_shared<VectorAppender>();
auto asyncAppender = std::make_shared<AsyncAppender>();
asyncAppender->setName(LOG4CXX_STR("async-testMultiThread"));
asyncAppender->addAppender(vectorAppender);
root->addAppender(asyncAppender);
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();
const std::vector<spi::LoggingEventPtr>& 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()
{
AppenderPtr nullPointerAppender(new NullPointerAppender());
AsyncAppenderPtr asyncAppender(new AsyncAppender());
asyncAppender->setName(LOG4CXX_STR("async-testBadAppender"));
asyncAppender->addAppender(nullPointerAppender);
asyncAppender->setBufferSize(5);
Pool p;
asyncAppender->activateOptions(p);
LoggerPtr root = Logger::getRootLogger();
root->addAppender(asyncAppender);
varia::FallbackErrorHandlerPtr errorHandler(new varia::FallbackErrorHandler());
errorHandler->setAppender(asyncAppender);
VectorAppenderPtr vectorAppender(new VectorAppender());
vectorAppender->setName(LOG4CXX_STR("async-memoryAppender"));
errorHandler->setBackupAppender(vectorAppender);
errorHandler->setLogger(root);
asyncAppender->setErrorHandler(errorHandler);
LOG4CXX_INFO(root, "Message");
std::this_thread::sleep_for( std::chrono::milliseconds( 30 ) );
LOGUNIT_ASSERT(errorHandler->errorReported());
LOG4CXX_INFO(root, "Message");
auto& v = vectorAppender->getVector();
LOGUNIT_ASSERT(0 < v.size());
}
/**
* Tests behavior when the the async buffer overflows.
*/
void testBufferOverflowBehavior()
{
auto blockableAppender = std::make_shared<BlockableVectorAppender>();
blockableAppender->setName(LOG4CXX_STR("async-blockableVector"));
auto async = std::make_shared<AsyncAppender>();
async->setName(LOG4CXX_STR("async-testBufferOverflowBehavior"));
async->addAppender(blockableAppender);
async->setBufferSize(5);
async->setLocationInfo(true);
async->setBlocking(false);
Pool p;
async->activateOptions(p);
auto rootLogger = Logger::getRootLogger();
rootLogger->addAppender(async);
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)
{
++levelCount[e->getLevel()];
if (e->getRenderedMessage().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, p, msg);
}
msg += LOG4CXX_STR(" ");
msg += LOG4CXX_STR("Discarded ");
StringHelper::toString(discardMessageCount, p, 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::spi::LocationInfo::getLocationUnavailable().getClassName(),
discardEvent->getLocationInformation().getClassName());
}
/**
* Tests behavior when the appender attached to a AsyncAppender adds logging events
*/
void testLoggingAppender()
{
auto loggingAppender = std::make_shared<LoggingVectorAppender>();
loggingAppender->setName(LOG4CXX_STR("loggingAppender"));
auto async = std::make_shared<AsyncAppender>();
async->setName(LOG4CXX_STR("withLoggingAppender"));
async->addAppender(loggingAppender);
async->setBufferSize(5);
async->setLocationInfo(true);
Pool p;
async->activateOptions(p);
auto rootLogger = Logger::getRootLogger();
rootLogger->addAppender(async);
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();
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], p, msg);
msg += LOG4CXX_STR(" appender ");
StringHelper::toString(eventCount[1], p, msg);
msg += LOG4CXX_STR(" discard ");
StringHelper::toString(discardMessageCount, p, msg);
LogLog::debug(msg);
}
LOGUNIT_ASSERT(12 < events.size());
// A race condition in AsyncAppender can result in a lost message when the dispatch thread is logging events
LOGUNIT_ASSERT(10 <= eventCount[0]);
}
#if LOG4CXX_HAS_DOMCONFIGURATOR
void testConfiguration()
{
auto status = xml::DOMConfigurator::configure("input/xml/asyncAppender1.xml");
LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
AsyncAppenderPtr asyncAppender = log4cxx::cast<AsyncAppender>(Logger::getRootLogger()->getAppender(LOG4CXX_STR("ASYNC")));
LOGUNIT_ASSERT(!(asyncAppender == 0));
LOGUNIT_ASSERT_EQUAL(100, asyncAppender->getBufferSize());
LOGUNIT_ASSERT_EQUAL(false, asyncAppender->getBlocking());
LOGUNIT_ASSERT_EQUAL(true, asyncAppender->getLocationInfo());
AppenderList nestedAppenders(asyncAppender->getAllAppenders());
// TODO:
// test seems to work okay, but have not found a working way to
// get a reference to the nested vector appender
//
// LOGUNIT_ASSERT_EQUAL((size_t) 1, nestedAppenders.size());
// VectorAppenderPtr vectorAppender(nestedAppenders[0]);
// LOGUNIT_ASSERT(0 != vectorAppender);
LoggerPtr root(Logger::getRootLogger());
size_t LEN = 20;
for (size_t i = 0; i < LEN; i++)
{
LOG4CXX_DEBUG_ASYNC(root, "message" << i);
}
asyncAppender->close();
// const std::vector<spi::LoggingEventPtr>& v = vectorAppender->getVector();
// LOGUNIT_ASSERT_EQUAL(LEN, v.size());
// LOGUNIT_ASSERT_EQUAL(true, vectorAppender->isClosed());
}
#endif
};
LOGUNIT_TEST_SUITE_REGISTRATION(AsyncAppenderTestCase);