1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under the BSD-style license found in the
3 // LICENSE file in the root directory of this source tree. An additional grant
4 // of patent rights can be found in the PATENTS file in the same directory.
9 #include "util/auto_roll_logger.h"
20 #include "port/port.h"
21 #include "rocksdb/db.h"
22 #include "util/logging.h"
23 #include "util/sync_point.h"
24 #include "util/testharness.h"
28 class NoSleepEnv
: public EnvWrapper
{
30 NoSleepEnv(Env
* base
) : EnvWrapper(base
) {}
31 virtual void SleepForMicroseconds(int micros
) override
{
32 fake_time_
+= static_cast<uint64_t>(micros
);
35 virtual uint64_t NowNanos() override
{ return fake_time_
* 1000; }
37 virtual uint64_t NowMicros() override
{ return fake_time_
; }
40 uint64_t fake_time_
= 6666666666;
44 class AutoRollLoggerTest
: public testing::Test
{
46 static void InitTestDb() {
48 // Replace all slashes in the path so windows CompSpec does not
50 std::string
testDir(kTestDir
);
51 std::replace_if(testDir
.begin(), testDir
.end(),
52 [](char ch
) { return ch
== '/'; }, '\\');
53 std::string deleteCmd
= "if exist " + testDir
+ " rd /s /q " + testDir
;
55 std::string deleteCmd
= "rm -rf " + kTestDir
;
57 ASSERT_TRUE(system(deleteCmd
.c_str()) == 0);
58 Env::Default()->CreateDir(kTestDir
);
61 void RollLogFileBySizeTest(AutoRollLogger
* logger
, size_t log_max_size
,
62 const std::string
& log_message
);
63 void RollLogFileByTimeTest(Env
*, AutoRollLogger
* logger
, size_t time
,
64 const std::string
& log_message
);
66 static const std::string kSampleMessage
;
67 static const std::string kTestDir
;
68 static const std::string kLogFile
;
69 static Env
* default_env
;
72 const std::string
AutoRollLoggerTest::kSampleMessage(
73 "this is the message to be written to the log file!!");
74 const std::string
AutoRollLoggerTest::kTestDir(test::TmpDir() + "/db_log_test");
75 const std::string
AutoRollLoggerTest::kLogFile(test::TmpDir() +
77 Env
* AutoRollLoggerTest::default_env
= Env::Default();
79 // In this test we only want to Log some simple log message with
80 // no format. LogMessage() provides such a simple interface and
81 // avoids the [format-security] warning which occurs when you
82 // call ROCKS_LOG_INFO(logger, log_message) directly.
84 void LogMessage(Logger
* logger
, const char* message
) {
85 ROCKS_LOG_INFO(logger
, "%s", message
);
88 void LogMessage(const InfoLogLevel log_level
, Logger
* logger
,
89 const char* message
) {
90 Log(log_level
, logger
, "%s", message
);
94 void AutoRollLoggerTest::RollLogFileBySizeTest(AutoRollLogger
* logger
,
96 const std::string
& log_message
) {
97 logger
->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL
);
98 // measure the size of each message, which is supposed
99 // to be equal or greater than log_message.size()
100 LogMessage(logger
, log_message
.c_str());
101 size_t message_size
= logger
->GetLogFileSize();
102 size_t current_log_size
= message_size
;
104 // Test the cases when the log file will not be rolled.
105 while (current_log_size
+ message_size
< log_max_size
) {
106 LogMessage(logger
, log_message
.c_str());
107 current_log_size
+= message_size
;
108 ASSERT_EQ(current_log_size
, logger
->GetLogFileSize());
111 // Now the log file will be rolled
112 LogMessage(logger
, log_message
.c_str());
113 // Since rotation is checked before actual logging, we need to
114 // trigger the rotation by logging another message.
115 LogMessage(logger
, log_message
.c_str());
117 ASSERT_TRUE(message_size
== logger
->GetLogFileSize());
120 void AutoRollLoggerTest::RollLogFileByTimeTest(Env
* env
, AutoRollLogger
* logger
,
122 const std::string
& log_message
) {
123 uint64_t expected_ctime
;
124 uint64_t actual_ctime
;
126 uint64_t total_log_size
;
127 EXPECT_OK(env
->GetFileSize(kLogFile
, &total_log_size
));
128 expected_ctime
= logger
->TEST_ctime();
129 logger
->SetCallNowMicrosEveryNRecords(0);
131 // -- Write to the log for several times, which is supposed
132 // to be finished before time.
133 for (int i
= 0; i
< 10; ++i
) {
134 env
->SleepForMicroseconds(50000);
135 LogMessage(logger
, log_message
.c_str());
136 EXPECT_OK(logger
->GetStatus());
137 // Make sure we always write to the same log file (by
138 // checking the create time);
140 actual_ctime
= logger
->TEST_ctime();
142 // Also make sure the log size is increasing.
143 EXPECT_EQ(expected_ctime
, actual_ctime
);
144 EXPECT_GT(logger
->GetLogFileSize(), total_log_size
);
145 total_log_size
= logger
->GetLogFileSize();
148 // -- Make the log file expire
149 env
->SleepForMicroseconds(static_cast<int>(time
* 1000000));
150 LogMessage(logger
, log_message
.c_str());
152 // At this time, the new log file should be created.
153 actual_ctime
= logger
->TEST_ctime();
154 EXPECT_LT(expected_ctime
, actual_ctime
);
155 EXPECT_LT(logger
->GetLogFileSize(), total_log_size
);
158 TEST_F(AutoRollLoggerTest
, RollLogFileBySize
) {
160 size_t log_max_size
= 1024 * 5;
162 AutoRollLogger
logger(Env::Default(), kTestDir
, "", log_max_size
, 0);
164 RollLogFileBySizeTest(&logger
, log_max_size
,
165 kSampleMessage
+ ":RollLogFileBySize");
168 TEST_F(AutoRollLoggerTest
, RollLogFileByTime
) {
169 NoSleepEnv
nse(Env::Default());
172 size_t log_size
= 1024 * 5;
175 // -- Test the existence of file during the server restart.
176 ASSERT_EQ(Status::NotFound(), default_env
->FileExists(kLogFile
));
177 AutoRollLogger
logger(&nse
, kTestDir
, "", log_size
, time
);
178 ASSERT_OK(default_env
->FileExists(kLogFile
));
180 RollLogFileByTimeTest(&nse
, &logger
, time
,
181 kSampleMessage
+ ":RollLogFileByTime");
184 TEST_F(AutoRollLoggerTest
, OpenLogFilesMultipleTimesWithOptionLog_max_size
) {
185 // If only 'log_max_size' options is specified, then every time
186 // when rocksdb is restarted, a new empty log file will be created.
189 // avoid complier's complaint of "comparison between signed
190 // and unsigned integer expressions" because literal 0 is
191 // treated as "singed".
193 size_t log_size
= 1024;
195 AutoRollLogger
* logger
= new AutoRollLogger(
196 Env::Default(), kTestDir
, "", log_size
, 0);
198 LogMessage(logger
, kSampleMessage
.c_str());
199 ASSERT_GT(logger
->GetLogFileSize(), kZero
);
202 // reopens the log file and an empty log file will be created.
203 logger
= new AutoRollLogger(
204 Env::Default(), kTestDir
, "", log_size
, 0);
205 ASSERT_EQ(logger
->GetLogFileSize(), kZero
);
209 TEST_F(AutoRollLoggerTest
, CompositeRollByTimeAndSizeLogger
) {
210 size_t time
= 2, log_max_size
= 1024 * 5;
214 NoSleepEnv
nse(Env::Default());
215 AutoRollLogger
logger(&nse
, kTestDir
, "", log_max_size
, time
);
217 // Test the ability to roll by size
218 RollLogFileBySizeTest(&logger
, log_max_size
,
219 kSampleMessage
+ ":CompositeRollByTimeAndSizeLogger");
221 // Test the ability to roll by Time
222 RollLogFileByTimeTest(&nse
, &logger
, time
,
223 kSampleMessage
+ ":CompositeRollByTimeAndSizeLogger");
227 // TODO: does not build for Windows because of PosixLogger use below. Need to
229 TEST_F(AutoRollLoggerTest
, CreateLoggerFromOptions
) {
231 NoSleepEnv
nse(Env::Default());
232 shared_ptr
<Logger
> logger
;
235 ASSERT_OK(CreateLoggerFromOptions(kTestDir
, options
, &logger
));
236 ASSERT_TRUE(dynamic_cast<PosixLogger
*>(logger
.get()));
240 options
.max_log_file_size
= 1024;
241 ASSERT_OK(CreateLoggerFromOptions(kTestDir
, options
, &logger
));
242 AutoRollLogger
* auto_roll_logger
=
243 dynamic_cast<AutoRollLogger
*>(logger
.get());
244 ASSERT_TRUE(auto_roll_logger
);
245 RollLogFileBySizeTest(
246 auto_roll_logger
, options
.max_log_file_size
,
247 kSampleMessage
+ ":CreateLoggerFromOptions - size");
252 options
.max_log_file_size
= 0;
253 options
.log_file_time_to_roll
= 2;
254 ASSERT_OK(CreateLoggerFromOptions(kTestDir
, options
, &logger
));
256 dynamic_cast<AutoRollLogger
*>(logger
.get());
257 RollLogFileByTimeTest(&nse
, auto_roll_logger
, options
.log_file_time_to_roll
,
258 kSampleMessage
+ ":CreateLoggerFromOptions - time");
260 // roll by both Time and size
262 options
.max_log_file_size
= 1024 * 5;
263 options
.log_file_time_to_roll
= 2;
264 ASSERT_OK(CreateLoggerFromOptions(kTestDir
, options
, &logger
));
266 dynamic_cast<AutoRollLogger
*>(logger
.get());
267 RollLogFileBySizeTest(auto_roll_logger
, options
.max_log_file_size
,
268 kSampleMessage
+ ":CreateLoggerFromOptions - both");
269 RollLogFileByTimeTest(&nse
, auto_roll_logger
, options
.log_file_time_to_roll
,
270 kSampleMessage
+ ":CreateLoggerFromOptions - both");
273 TEST_F(AutoRollLoggerTest
, LogFlushWhileRolling
) {
275 shared_ptr
<Logger
> logger
;
278 options
.max_log_file_size
= 1024 * 5;
279 ASSERT_OK(CreateLoggerFromOptions(kTestDir
, options
, &logger
));
280 AutoRollLogger
* auto_roll_logger
=
281 dynamic_cast<AutoRollLogger
*>(logger
.get());
282 ASSERT_TRUE(auto_roll_logger
);
283 rocksdb::port::Thread flush_thread
;
286 // (1) Need to pin the old logger before beginning the roll, as rolling grabs
287 // the mutex, which would prevent us from accessing the old logger. This
288 // also marks flush_thread with AutoRollLogger::Flush:PinnedLogger.
289 // (2) Need to reset logger during PosixLogger::Flush() to exercise a race
290 // condition case, which is executing the flush with the pinned (old)
291 // logger after auto-roll logger has cut over to a new logger.
292 // (3) PosixLogger::Flush() happens in both threads but its SyncPoints only
293 // are enabled in flush_thread (the one pinning the old logger).
294 rocksdb::SyncPoint::GetInstance()->LoadDependencyAndMarkers(
295 {{"AutoRollLogger::Flush:PinnedLogger",
296 "AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit"},
297 {"PosixLogger::Flush:Begin1",
298 "AutoRollLogger::ResetLogger:BeforeNewLogger"},
299 {"AutoRollLogger::ResetLogger:AfterNewLogger",
300 "PosixLogger::Flush:Begin2"}},
301 {{"AutoRollLogger::Flush:PinnedLogger", "PosixLogger::Flush:Begin1"},
302 {"AutoRollLogger::Flush:PinnedLogger", "PosixLogger::Flush:Begin2"}});
303 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
305 flush_thread
= port::Thread ([&]() { auto_roll_logger
->Flush(); });
307 "AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit");
308 RollLogFileBySizeTest(auto_roll_logger
, options
.max_log_file_size
,
309 kSampleMessage
+ ":LogFlushWhileRolling");
311 rocksdb::SyncPoint::GetInstance()->DisableProcessing();
316 TEST_F(AutoRollLoggerTest
, InfoLogLevel
) {
319 size_t log_size
= 8192;
320 size_t log_lines
= 0;
321 // an extra-scope to force the AutoRollLogger to flush the log file when it
322 // becomes out of scope.
324 AutoRollLogger
logger(Env::Default(), kTestDir
, "", log_size
, 0);
325 for (int log_level
= InfoLogLevel::HEADER_LEVEL
;
326 log_level
>= InfoLogLevel::DEBUG_LEVEL
; log_level
--) {
327 logger
.SetInfoLogLevel((InfoLogLevel
)log_level
);
328 for (int log_type
= InfoLogLevel::DEBUG_LEVEL
;
329 log_type
<= InfoLogLevel::HEADER_LEVEL
; log_type
++) {
330 // log messages with log level smaller than log_level will not be
332 LogMessage((InfoLogLevel
)log_type
, &logger
, kSampleMessage
.c_str());
334 log_lines
+= InfoLogLevel::HEADER_LEVEL
- log_level
+ 1;
336 for (int log_level
= InfoLogLevel::HEADER_LEVEL
;
337 log_level
>= InfoLogLevel::DEBUG_LEVEL
; log_level
--) {
338 logger
.SetInfoLogLevel((InfoLogLevel
)log_level
);
340 // again, messages with level smaller than log_level will not be logged.
341 ROCKS_LOG_HEADER(&logger
, "%s", kSampleMessage
.c_str());
342 ROCKS_LOG_DEBUG(&logger
, "%s", kSampleMessage
.c_str());
343 ROCKS_LOG_INFO(&logger
, "%s", kSampleMessage
.c_str());
344 ROCKS_LOG_WARN(&logger
, "%s", kSampleMessage
.c_str());
345 ROCKS_LOG_ERROR(&logger
, "%s", kSampleMessage
.c_str());
346 ROCKS_LOG_FATAL(&logger
, "%s", kSampleMessage
.c_str());
347 log_lines
+= InfoLogLevel::HEADER_LEVEL
- log_level
+ 1;
350 std::ifstream
inFile(AutoRollLoggerTest::kLogFile
.c_str());
351 size_t lines
= std::count(std::istreambuf_iterator
<char>(inFile
),
352 std::istreambuf_iterator
<char>(), '\n');
353 ASSERT_EQ(log_lines
, lines
);
357 // Test the logger Header function for roll over logs
358 // We expect the new logs creates as roll over to carry the headers specified
359 static std::vector
<std::string
> GetOldFileNames(const std::string
& path
) {
360 std::vector
<std::string
> ret
;
362 const std::string dirname
= path
.substr(/*start=*/0, path
.find_last_of("/"));
363 const std::string fname
= path
.substr(path
.find_last_of("/") + 1);
365 std::vector
<std::string
> children
;
366 Env::Default()->GetChildren(dirname
, &children
);
368 // We know that the old log files are named [path]<something>
369 // Return all entities that match the pattern
370 for (auto& child
: children
) {
371 if (fname
!= child
&& child
.find(fname
) == 0) {
372 ret
.push_back(dirname
+ "/" + child
);
379 // Return the number of lines where a given pattern was found in the file
380 static size_t GetLinesCount(const std::string
& fname
,
381 const std::string
& pattern
) {
382 std::stringstream ssbuf
;
386 std::ifstream
inFile(fname
.c_str());
387 ssbuf
<< inFile
.rdbuf();
389 while (getline(ssbuf
, line
)) {
390 if (line
.find(pattern
) != std::string::npos
) {
398 TEST_F(AutoRollLoggerTest
, LogHeaderTest
) {
399 static const size_t MAX_HEADERS
= 10;
400 static const size_t LOG_MAX_SIZE
= 1024 * 5;
401 static const std::string HEADER_STR
= "Log header line";
403 // test_num == 0 -> standard call to Header()
404 // test_num == 1 -> call to Log() with InfoLogLevel::HEADER_LEVEL
405 for (int test_num
= 0; test_num
< 2; test_num
++) {
409 AutoRollLogger
logger(Env::Default(), kTestDir
, /*db_log_dir=*/ "",
410 LOG_MAX_SIZE
, /*log_file_time_to_roll=*/ 0);
413 // Log some headers explicitly using Header()
414 for (size_t i
= 0; i
< MAX_HEADERS
; i
++) {
415 Header(&logger
, "%s %d", HEADER_STR
.c_str(), i
);
417 } else if (test_num
== 1) {
418 // HEADER_LEVEL should make this behave like calling Header()
419 for (size_t i
= 0; i
< MAX_HEADERS
; i
++) {
420 ROCKS_LOG_HEADER(&logger
, "%s %d", HEADER_STR
.c_str(), i
);
424 const std::string newfname
= logger
.TEST_log_fname();
426 // Log enough data to cause a roll over
428 for (size_t iter
= 0; iter
< 2; iter
++) {
429 while (logger
.GetLogFileSize() < LOG_MAX_SIZE
) {
430 Info(&logger
, (kSampleMessage
+ ":LogHeaderTest line %d").c_str(), i
);
434 Info(&logger
, "Rollover");
437 // Flush the log for the latest file
440 const auto oldfiles
= GetOldFileNames(newfname
);
442 ASSERT_EQ(oldfiles
.size(), (size_t) 2);
444 for (auto& oldfname
: oldfiles
) {
445 // verify that the files rolled over
446 ASSERT_NE(oldfname
, newfname
);
447 // verify that the old log contains all the header logs
448 ASSERT_EQ(GetLinesCount(oldfname
, HEADER_STR
), MAX_HEADERS
);
453 TEST_F(AutoRollLoggerTest
, LogFileExistence
) {
455 rocksdb::Options options
;
457 // Replace all slashes in the path so windows CompSpec does not
459 std::string
testDir(kTestDir
);
460 std::replace_if(testDir
.begin(), testDir
.end(),
461 [](char ch
) { return ch
== '/'; }, '\\');
462 std::string deleteCmd
= "if exist " + testDir
+ " rd /s /q " + testDir
;
464 std::string deleteCmd
= "rm -rf " + kTestDir
;
466 ASSERT_EQ(system(deleteCmd
.c_str()), 0);
467 options
.max_log_file_size
= 100 * 1024 * 1024;
468 options
.create_if_missing
= true;
469 ASSERT_OK(rocksdb::DB::Open(options
, kTestDir
, &db
));
470 ASSERT_OK(default_env
->FileExists(kLogFile
));
474 } // namespace rocksdb
476 int main(int argc
, char** argv
) {
477 ::testing::InitGoogleTest(&argc
, argv
);
478 return RUN_ALL_TESTS();
484 int main(int argc
, char** argv
) {
486 "SKIPPED as AutoRollLogger is not supported in ROCKSDB_LITE\n");
490 #endif // !ROCKSDB_LITE