This is an automated email from the ASF dual-hosted git repository. swebb2066 pushed a commit to branch flush_buffered_output in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git
commit 1ac4d212fd3e5cc68499b2751123fc33d0e9d7f4 Author: Stephen Webb <[email protected]> AuthorDate: Sat Nov 1 12:17:46 2025 +1100 Ensure all buffered log message are written at exit --- src/test/cpp/fileappendertest.cpp | 141 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 4 deletions(-) diff --git a/src/test/cpp/fileappendertest.cpp b/src/test/cpp/fileappendertest.cpp index 68e89b41..6474cc25 100644 --- a/src/test/cpp/fileappendertest.cpp +++ b/src/test/cpp/fileappendertest.cpp @@ -15,19 +15,24 @@ * limitations under the License. */ #include <log4cxx/basicconfigurator.h> -#include <log4cxx/fileappender.h> #include <log4cxx/logmanager.h> #include <log4cxx/patternlayout.h> #include <log4cxx/helpers/pool.h> #include <log4cxx/helpers/loglog.h> #include <log4cxx/helpers/stringhelper.h> +#include <log4cxx/helpers/transcoder.h> +#include <log4cxx/helpers/fileoutputstream.h> +#include <log4cxx/rolling/rollingfileappender.h> +#include <log4cxx/rolling/timebasedrollingpolicy.h> #include "logunit.h" #include <apr_time.h> +#include <apr_thread_proc.h> +#include <fstream> using namespace log4cxx; using namespace log4cxx::helpers; -auto getLogger(const std::string& name = std::string()) -> LoggerPtr { +auto getLogger(const std::string& name = {}) -> LoggerPtr { static struct log4cxx_initializer { log4cxx_initializer() { auto layout = std::make_shared<PatternLayout>(LOG4CXX_STR("%d %m%n")); @@ -59,7 +64,9 @@ LOGUNIT_CLASS(FileAppenderTest) LOGUNIT_TEST(testDirectoryCreation); LOGUNIT_TEST(testgetSetThreshold); LOGUNIT_TEST(testIsAsSevereAsThreshold); - LOGUNIT_TEST(testBufferedOutput); + LOGUNIT_TEST(testPeriodicFlush); + LOGUNIT_TEST(writeFinalBufferOutput); + LOGUNIT_TEST(checkFinalBufferOutput); LOGUNIT_TEST_SUITE_END(); #ifdef _DEBUG @@ -119,7 +126,8 @@ public: LOGUNIT_ASSERT(appender->isAsSevereAsThreshold(debug)); } - void testBufferedOutput() + // Check a file is periodically flushed + void testPeriodicFlush() { auto logger = getLogger(); int requiredMsgCount = 10000; @@ -136,6 +144,8 @@ public: // wait 1.2 sec and check the buffer is flushed apr_sleep(1200000); size_t flushedLength = file.length(p); + + // Check the file extended if (helpers::LogLog::isDebugEnabled()) { LogString msg(LOG4CXX_STR("initialLength ")); @@ -146,6 +156,129 @@ public: } LOGUNIT_ASSERT(initialLength < flushedLength); } + + // Used to check the buffer is flushed at exit + void writeFinalBufferOutput() + { + int requiredMsgCount = 100; + auto logger = getLogger(LOG4CXX_STR("100message")); + + // Set up a new file + LogString dir{ LOG4CXX_STR("output/newdir") }; + auto writer = std::make_shared<rolling::RollingFileAppender>(); + writer->setLayout(std::make_shared<PatternLayout>(LOG4CXX_STR("%d %m%n"))); + writer->setFile(dir + LOG4CXX_STR("/100message.log")); + writer->setBufferedIO(true); + auto policy = std::make_shared<rolling::TimeBasedRollingPolicy>(); + policy->setFileNamePattern(dir + LOG4CXX_STR("/100message-%d{yyyy-MM-dd-HH-mm-ss-SSS}.log")); + writer->setRollingPolicy(policy); + helpers::Pool p; + writer->activateOptions(p); + logger->setAdditivity(false); + logger->addAppender(writer); + + for ( int x = 0; x < requiredMsgCount; x++ ) + { + LOG4CXX_INFO( logger, "This is test message " << x ); + } + } + + void checkFinalBufferOutput() + { + helpers::Pool p; + // start a separate instance of this to write messages are in the file + helpers::FileOutputStream output(LOG4CXX_STR("output/newdir/100message-writer.out"), false); + auto thisProgram = GetExecutableFileName(); + helpers::LogLog::debug(LOG4CXX_STR("thisProgram: ") + thisProgram); + const char* args[] = {thisProgram.c_str(), "writeFinalBufferOutput", 0}; + apr_procattr_t* attr = NULL; + setTestAttributes(&attr, output.getFilePtr(), p); + apr_proc_t pid; + startTestInstance(&pid, attr, args, p); + + int exitCode; + apr_exit_why_e reason; + apr_proc_wait(&pid, &exitCode, &reason, APR_WAIT); + if (exitCode != 0) + { + LogString msg = LOG4CXX_STR("child exit code: "); + helpers::StringHelper::toString(exitCode, p, msg); + msg += LOG4CXX_STR("; reason: "); + helpers::StringHelper::toString(reason, p, msg); + helpers::LogLog::warn(msg); + } + LOGUNIT_ASSERT_EQUAL(exitCode, 0); + + // Check all required messages are in the file + std::ifstream input("output/newdir/100message.log"); + std::vector<int> messageCount; + int lineCount{ 0 }; + for (std::string line; std::getline(input, line);) + { + ++lineCount; + 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); + } + } + } + LogString msg = LOG4CXX_STR("lineCount: "); + helpers::StringHelper::toString(lineCount, p, msg); + helpers::LogLog::debug(msg); + for (auto& count : messageCount) + LOGUNIT_ASSERT_EQUAL(count, messageCount.front()); + } + +private: + + void setTestAttributes(apr_procattr_t** attr, apr_file_t* output, helpers::Pool& p) + { + if (apr_procattr_create(attr, p.getAPRPool()) != APR_SUCCESS) + { + LOGUNIT_FAIL("apr_procattr_create"); + } + if (apr_procattr_cmdtype_set(*attr, APR_PROGRAM) != APR_SUCCESS) + { + LOGUNIT_FAIL("apr_procattr_cmdtype_set"); + } + if (apr_procattr_child_out_set(*attr, output, NULL) != APR_SUCCESS) + { + LOGUNIT_FAIL("apr_procattr_child_out_set"); + } + if (apr_procattr_child_err_set(*attr, output, NULL) != APR_SUCCESS) + { + LOGUNIT_FAIL("apr_procattr_child_err_set"); + } + } + + void startTestInstance(apr_proc_t* pid, apr_procattr_t* attr, const char** argv, helpers::Pool& p) + { + if (apr_proc_create(pid, argv[0], argv, NULL, attr, p.getAPRPool()) != APR_SUCCESS) + { + LOGUNIT_FAIL("apr_proc_create"); + } + } + + std::string GetExecutableFileName() + { + auto lsProgramFilePath = spi::Configurator::properties().getProperty(LOG4CXX_STR("PROGRAM_FILE_PATH")); + LOG4CXX_ENCODE_CHAR(programFilePath, lsProgramFilePath); + return programFilePath; + } }; LOGUNIT_TEST_SUITE_REGISTRATION(FileAppenderTest);
