Skip to content

Commit 397c782

Browse files
authored
Ensure all buffered log message are written at exit (#560)
* Add FAQ answer for why buffered asynchronous output is not the default
1 parent 8c7aecd commit 397c782

File tree

2 files changed

+149
-4
lines changed

2 files changed

+149
-4
lines changed

src/site/markdown/faq.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ Frequently Asked Technical Questions {#faq}
2222
-->
2323
[TOC]
2424

25+
## Why doesn't Log4cxx use buffered asynchronous output by default?{#buffered_output}
26+
27+
Being able to examine logged values is critically important during application development.
28+
Buffering output prevents the log file from containing the lastest messages in two situations:
29+
-# when a debugger stops the application at a break point
30+
-# when the application aborts abnormally
31+
(e.g. calls [std::terminate](https://en.cppreference.com/w/cpp/error/terminate.html)
32+
or the operating system detects a fault)
33+
34+
Debugging a program becomes extremely confusing when the log file
35+
does not contain the most recently written logging requests.
36+
2537
## How do I add a custom level to Apache Log4cxx?{#custom_levels}
2638

2739
This is a common topic for all the Apache logging frameworks and typically motivated to try to

src/test/cpp/fileappendertest.cpp

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,24 @@
1515
* limitations under the License.
1616
*/
1717
#include <log4cxx/basicconfigurator.h>
18-
#include <log4cxx/fileappender.h>
1918
#include <log4cxx/logmanager.h>
2019
#include <log4cxx/patternlayout.h>
2120
#include <log4cxx/helpers/pool.h>
2221
#include <log4cxx/helpers/loglog.h>
2322
#include <log4cxx/helpers/stringhelper.h>
23+
#include <log4cxx/helpers/transcoder.h>
24+
#include <log4cxx/helpers/fileoutputstream.h>
25+
#include <log4cxx/rolling/rollingfileappender.h>
26+
#include <log4cxx/rolling/timebasedrollingpolicy.h>
2427
#include "logunit.h"
2528
#include <apr_time.h>
29+
#include <apr_thread_proc.h>
30+
#include <fstream>
2631

2732
using namespace log4cxx;
2833
using namespace log4cxx::helpers;
2934

30-
auto getLogger(const std::string& name = std::string()) -> LoggerPtr {
35+
auto getLogger(const LogString& name = {}) -> LoggerPtr {
3136
static struct log4cxx_initializer {
3237
log4cxx_initializer() {
3338
auto layout = std::make_shared<PatternLayout>(LOG4CXX_STR("%d %m%n"));
@@ -59,7 +64,9 @@ LOGUNIT_CLASS(FileAppenderTest)
5964
LOGUNIT_TEST(testDirectoryCreation);
6065
LOGUNIT_TEST(testgetSetThreshold);
6166
LOGUNIT_TEST(testIsAsSevereAsThreshold);
62-
LOGUNIT_TEST(testBufferedOutput);
67+
LOGUNIT_TEST(testPeriodicFlush);
68+
LOGUNIT_TEST(writeFinalBufferOutput);
69+
LOGUNIT_TEST(checkFinalBufferOutput);
6370
LOGUNIT_TEST_SUITE_END();
6471

6572
#ifdef _DEBUG
@@ -119,7 +126,8 @@ LOGUNIT_CLASS(FileAppenderTest)
119126
LOGUNIT_ASSERT(appender->isAsSevereAsThreshold(debug));
120127
}
121128

122-
void testBufferedOutput()
129+
// Check a file is periodically flushed
130+
void testPeriodicFlush()
123131
{
124132
auto logger = getLogger();
125133
int requiredMsgCount = 10000;
@@ -136,6 +144,8 @@ LOGUNIT_CLASS(FileAppenderTest)
136144
// wait 1.2 sec and check the buffer is flushed
137145
apr_sleep(1200000);
138146
size_t flushedLength = file.length(p);
147+
148+
// Check the file extended
139149
if (helpers::LogLog::isDebugEnabled())
140150
{
141151
LogString msg(LOG4CXX_STR("initialLength "));
@@ -146,6 +156,129 @@ LOGUNIT_CLASS(FileAppenderTest)
146156
}
147157
LOGUNIT_ASSERT(initialLength < flushedLength);
148158
}
159+
160+
// Used to check the buffer is flushed at exit
161+
void writeFinalBufferOutput()
162+
{
163+
int requiredMsgCount = 100;
164+
auto logger = getLogger(LOG4CXX_STR("100message"));
165+
166+
// Set up a new file
167+
LogString dir{ LOG4CXX_STR("output/newdir") };
168+
auto writer = std::make_shared<rolling::RollingFileAppender>();
169+
writer->setLayout(std::make_shared<PatternLayout>(LOG4CXX_STR("%d %m%n")));
170+
writer->setFile(dir + LOG4CXX_STR("/100message.log"));
171+
writer->setBufferedIO(true);
172+
writer->setBufferedSeconds(1);
173+
auto policy = std::make_shared<rolling::TimeBasedRollingPolicy>();
174+
policy->setFileNamePattern(dir + LOG4CXX_STR("/100message-%d{yyyy}.log"));
175+
writer->setRollingPolicy(policy);
176+
helpers::Pool p;
177+
writer->activateOptions(p);
178+
logger->setAdditivity(false);
179+
logger->addAppender(writer);
180+
181+
for ( int x = 0; x < requiredMsgCount; x++ )
182+
{
183+
LOG4CXX_INFO( logger, "This is test message " << x );
184+
}
185+
}
186+
187+
void checkFinalBufferOutput()
188+
{
189+
helpers::Pool p;
190+
// start a separate instance of this to write messages to the file
191+
helpers::FileOutputStream output(LOG4CXX_STR("output/newdir/100message-writer.out"), false);
192+
auto thisProgram = GetExecutableFileName();
193+
const char* args[] = {thisProgram.c_str(), "writeFinalBufferOutput", 0};
194+
apr_procattr_t* attr = NULL;
195+
setTestAttributes(&attr, output.getFilePtr(), p);
196+
apr_proc_t pid;
197+
startTestInstance(&pid, attr, args, p);
198+
199+
int exitCode;
200+
apr_exit_why_e reason;
201+
apr_proc_wait(&pid, &exitCode, &reason, APR_WAIT);
202+
if (exitCode != 0)
203+
{
204+
LogString msg = LOG4CXX_STR("child exit code: ");
205+
helpers::StringHelper::toString(exitCode, p, msg);
206+
msg += LOG4CXX_STR("; reason: ");
207+
helpers::StringHelper::toString(reason, p, msg);
208+
helpers::LogLog::warn(msg);
209+
}
210+
LOGUNIT_ASSERT_EQUAL(exitCode, 0);
211+
212+
// Check all required messages are in the file
213+
std::ifstream input("output/newdir/100message.log");
214+
std::vector<int> messageCount;
215+
int lineCount{ 0 };
216+
for (std::string line; std::getline(input, line);)
217+
{
218+
++lineCount;
219+
auto pos = line.rfind(' ');
220+
if (line.npos != pos && pos + 1 < line.size())
221+
{
222+
try
223+
{
224+
auto msgNumber = std::stoull(line.substr(pos));
225+
if (messageCount.size() <= msgNumber)
226+
messageCount.resize(msgNumber + 1);
227+
++messageCount[msgNumber];
228+
}
229+
catch (std::exception const& ex)
230+
{
231+
LogString msg;
232+
helpers::Transcoder::decode(ex.what(), msg);
233+
msg += LOG4CXX_STR(" processing\n");
234+
helpers::Transcoder::decode(line, msg);
235+
helpers::LogLog::warn(msg);
236+
}
237+
}
238+
}
239+
LogString msg = LOG4CXX_STR("lineCount: ");
240+
helpers::StringHelper::toString(lineCount, p, msg);
241+
helpers::LogLog::debug(msg);
242+
for (auto& count : messageCount)
243+
LOGUNIT_ASSERT_EQUAL(count, messageCount.front());
244+
}
245+
246+
private:
247+
248+
void setTestAttributes(apr_procattr_t** attr, apr_file_t* output, helpers::Pool& p)
249+
{
250+
if (apr_procattr_create(attr, p.getAPRPool()) != APR_SUCCESS)
251+
{
252+
LOGUNIT_FAIL("apr_procattr_create");
253+
}
254+
if (apr_procattr_cmdtype_set(*attr, APR_PROGRAM) != APR_SUCCESS)
255+
{
256+
LOGUNIT_FAIL("apr_procattr_cmdtype_set");
257+
}
258+
if (apr_procattr_child_out_set(*attr, output, NULL) != APR_SUCCESS)
259+
{
260+
LOGUNIT_FAIL("apr_procattr_child_out_set");
261+
}
262+
if (apr_procattr_child_err_set(*attr, output, NULL) != APR_SUCCESS)
263+
{
264+
LOGUNIT_FAIL("apr_procattr_child_err_set");
265+
}
266+
}
267+
268+
void startTestInstance(apr_proc_t* pid, apr_procattr_t* attr, const char** argv, helpers::Pool& p)
269+
{
270+
if (apr_proc_create(pid, argv[0], argv, NULL, attr, p.getAPRPool()) != APR_SUCCESS)
271+
{
272+
LOGUNIT_FAIL("apr_proc_create");
273+
}
274+
}
275+
276+
std::string GetExecutableFileName()
277+
{
278+
auto lsProgramFilePath = spi::Configurator::properties().getProperty(LOG4CXX_STR("PROGRAM_FILE_PATH"));
279+
LOG4CXX_ENCODE_CHAR(programFilePath, lsProgramFilePath);
280+
return programFilePath;
281+
}
149282
};
150283

151284
LOGUNIT_TEST_SUITE_REGISTRATION(FileAppenderTest);

0 commit comments

Comments
 (0)