#include "logging/auto_roll_logger.h"
#include <algorithm>
#include "file/filename.h"
#include "logging/logging.h"
#include "rocksdb/env.h"
#include "rocksdb/file_system.h"
#include "rocksdb/system_clock.h"
#include "util/mutexlock.h"
namespace ROCKSDB_NAMESPACE {
#ifndef ROCKSDB_LITE
AutoRollLogger::AutoRollLogger(const std::shared_ptr<FileSystem>& fs,
const std::shared_ptr<SystemClock>& clock,
const std::string& dbname,
const std::string& db_log_dir,
size_t log_max_size,
size_t log_file_time_to_roll,
size_t keep_log_file_num,
const InfoLogLevel log_level)
: Logger(log_level),
dbname_(dbname),
db_log_dir_(db_log_dir),
fs_(fs),
clock_(clock),
status_(Status::OK()),
kMaxLogFileSize(log_max_size),
kLogFileTimeToRoll(log_file_time_to_roll),
kKeepLogFileNum(keep_log_file_num),
cached_now(static_cast<uint64_t>(clock_->NowMicros() * 1e-6)),
ctime_(cached_now),
cached_now_access_count(0),
call_NowMicros_every_N_records_(100),
mutex_() {
Status s = fs->GetAbsolutePath(dbname, io_options_, &db_absolute_path_,
&io_context_);
if (s.IsNotSupported()) {
db_absolute_path_ = dbname;
} else {
status_ = s;
}
log_fname_ = InfoLogFileName(dbname_, db_absolute_path_, db_log_dir_);
if (fs_->FileExists(log_fname_, io_options_, &io_context_).ok()) {
RollLogFile();
}
GetExistingFiles();
s = ResetLogger();
if (s.ok() && status_.ok()) {
status_ = TrimOldLogFiles();
}
}
Status AutoRollLogger::ResetLogger() {
TEST_SYNC_POINT("AutoRollLogger::ResetLogger:BeforeNewLogger");
status_ = fs_->NewLogger(log_fname_, io_options_, &logger_, &io_context_);
TEST_SYNC_POINT("AutoRollLogger::ResetLogger:AfterNewLogger");
if (!status_.ok()) {
return status_;
}
assert(logger_);
logger_->SetInfoLogLevel(Logger::GetInfoLogLevel());
if (logger_->GetLogFileSize() == Logger::kDoNotSupportGetLogFileSize) {
status_ = Status::NotSupported(
"The underlying logger doesn't support GetLogFileSize()");
}
if (status_.ok()) {
cached_now = static_cast<uint64_t>(clock_->NowMicros() * 1e-6);
ctime_ = cached_now;
cached_now_access_count = 0;
}
return status_;
}
void AutoRollLogger::RollLogFile() {
uint64_t now = clock_->NowMicros();
std::string old_fname;
do {
old_fname = OldInfoLogFileName(
dbname_, now, db_absolute_path_, db_log_dir_);
now++;
} while (fs_->FileExists(old_fname, io_options_, &io_context_).ok());
Status s = fs_->RenameFile(log_fname_, old_fname, io_options_, &io_context_);
if (!s.ok()) {
}
old_log_files_.push(old_fname);
}
void AutoRollLogger::GetExistingFiles() {
{
std::queue<std::string> empty;
std::swap(old_log_files_, empty);
}
std::string parent_dir;
std::vector<std::string> info_log_files;
Status s =
GetInfoLogFiles(fs_, db_log_dir_, dbname_, &parent_dir, &info_log_files);
if (status_.ok()) {
status_ = s;
}
std::sort(info_log_files.begin(), info_log_files.end());
for (const std::string& f : info_log_files) {
old_log_files_.push(parent_dir + "/" + f);
}
}
Status AutoRollLogger::TrimOldLogFiles() {
while (!old_log_files_.empty() && old_log_files_.size() >= kKeepLogFileNum) {
Status s =
fs_->DeleteFile(old_log_files_.front(), io_options_, &io_context_);
old_log_files_.pop();
if (!s.ok()) {
return s;
}
}
return Status::OK();
}
std::string AutoRollLogger::ValistToString(const char* format,
va_list args) const {
static const int MAXBUFFERSIZE = 1024;
char buffer[MAXBUFFERSIZE];
int count = vsnprintf(buffer, MAXBUFFERSIZE, format, args);
(void) count;
assert(count >= 0);
return buffer;
}
void AutoRollLogger::LogInternal(const char* format, ...) {
mutex_.AssertHeld();
if (!logger_) {
return;
}
va_list args;
va_start(args, format);
logger_->Logv(format, args);
va_end(args);
}
void AutoRollLogger::Logv(const char* format, va_list ap) {
assert(GetStatus().ok());
if (!logger_) {
return;
}
std::shared_ptr<Logger> logger;
{
MutexLock l(&mutex_);
if ((kLogFileTimeToRoll > 0 && LogExpired()) ||
(kMaxLogFileSize > 0 && logger_->GetLogFileSize() >= kMaxLogFileSize)) {
RollLogFile();
Status s = ResetLogger();
Status s2 = TrimOldLogFiles();
if (!s.ok()) {
return;
}
WriteHeaderInfo();
if (!s2.ok()) {
ROCKS_LOG_WARN(logger.get(), "Fail to trim old info log file: %s",
s2.ToString().c_str());
}
}
logger = logger_;
}
logger->Logv(format, ap);
}
void AutoRollLogger::WriteHeaderInfo() {
mutex_.AssertHeld();
for (auto& header : headers_) {
LogInternal("%s", header.c_str());
}
}
void AutoRollLogger::LogHeader(const char* format, va_list args) {
if (!logger_) {
return;
}
va_list tmp;
va_copy(tmp, args);
std::string data = ValistToString(format, tmp);
va_end(tmp);
MutexLock l(&mutex_);
headers_.push_back(data);
logger_->Logv(format, args);
}
bool AutoRollLogger::LogExpired() {
if (cached_now_access_count >= call_NowMicros_every_N_records_) {
cached_now = static_cast<uint64_t>(clock_->NowMicros() * 1e-6);
cached_now_access_count = 0;
}
++cached_now_access_count;
return cached_now >= ctime_ + kLogFileTimeToRoll;
}
#endif
Status CreateLoggerFromOptions(const std::string& dbname,
const DBOptions& options,
std::shared_ptr<Logger>* logger) {
if (options.info_log) {
*logger = options.info_log;
return Status::OK();
}
Env* env = options.env;
std::string db_absolute_path;
Status s = env->GetAbsolutePath(dbname, &db_absolute_path);
if (!s.ok()) {
return s;
}
std::string fname =
InfoLogFileName(dbname, db_absolute_path, options.db_log_dir);
const auto& clock = env->GetSystemClock();
env->CreateDirIfMissing(dbname)
.PermitUncheckedError(); #ifndef ROCKSDB_LITE
if (options.log_file_time_to_roll > 0 || options.max_log_file_size > 0) {
AutoRollLogger* result = new AutoRollLogger(
env->GetFileSystem(), clock, dbname, options.db_log_dir,
options.max_log_file_size, options.log_file_time_to_roll,
options.keep_log_file_num, options.info_log_level);
s = result->GetStatus();
if (!s.ok()) {
delete result;
} else {
logger->reset(result);
}
return s;
}
#endif env->RenameFile(
fname, OldInfoLogFileName(dbname, clock->NowMicros(), db_absolute_path,
options.db_log_dir))
.PermitUncheckedError();
s = env->NewLogger(fname, logger);
if (logger->get() != nullptr) {
(*logger)->SetInfoLogLevel(options.info_log_level);
}
return s;
}
}