1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5 //
6 #include "logging/auto_roll_logger.h"
7
8 #include <algorithm>
9 #include "file/filename.h"
10 #include "logging/logging.h"
11 #include "util/mutexlock.h"
12
13 namespace ROCKSDB_NAMESPACE {
14
15 #ifndef ROCKSDB_LITE
16 // -- AutoRollLogger
17
AutoRollLogger(Env * env,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)18 AutoRollLogger::AutoRollLogger(Env* env, const std::string& dbname,
19 const std::string& db_log_dir,
20 size_t log_max_size,
21 size_t log_file_time_to_roll,
22 size_t keep_log_file_num,
23 const InfoLogLevel log_level)
24 : Logger(log_level),
25 dbname_(dbname),
26 db_log_dir_(db_log_dir),
27 env_(env),
28 status_(Status::OK()),
29 kMaxLogFileSize(log_max_size),
30 kLogFileTimeToRoll(log_file_time_to_roll),
31 kKeepLogFileNum(keep_log_file_num),
32 cached_now(static_cast<uint64_t>(env_->NowMicros() * 1e-6)),
33 ctime_(cached_now),
34 cached_now_access_count(0),
35 call_NowMicros_every_N_records_(100),
36 mutex_() {
37 Status s = env->GetAbsolutePath(dbname, &db_absolute_path_);
38 if (s.IsNotSupported()) {
39 db_absolute_path_ = dbname;
40 } else {
41 status_ = s;
42 }
43 log_fname_ = InfoLogFileName(dbname_, db_absolute_path_, db_log_dir_);
44 if (env_->FileExists(log_fname_).ok()) {
45 RollLogFile();
46 }
47 GetExistingFiles();
48 ResetLogger();
49 if (status_.ok()) {
50 status_ = TrimOldLogFiles();
51 }
52 }
53
ResetLogger()54 Status AutoRollLogger::ResetLogger() {
55 TEST_SYNC_POINT("AutoRollLogger::ResetLogger:BeforeNewLogger");
56 status_ = env_->NewLogger(log_fname_, &logger_);
57 TEST_SYNC_POINT("AutoRollLogger::ResetLogger:AfterNewLogger");
58
59 if (!status_.ok()) {
60 return status_;
61 }
62 assert(logger_);
63 logger_->SetInfoLogLevel(Logger::GetInfoLogLevel());
64
65 if (logger_->GetLogFileSize() == Logger::kDoNotSupportGetLogFileSize) {
66 status_ = Status::NotSupported(
67 "The underlying logger doesn't support GetLogFileSize()");
68 }
69 if (status_.ok()) {
70 cached_now = static_cast<uint64_t>(env_->NowMicros() * 1e-6);
71 ctime_ = cached_now;
72 cached_now_access_count = 0;
73 }
74
75 return status_;
76 }
77
RollLogFile()78 void AutoRollLogger::RollLogFile() {
79 // This function is called when log is rotating. Two rotations
80 // can happen quickly (NowMicro returns same value). To not overwrite
81 // previous log file we increment by one micro second and try again.
82 uint64_t now = env_->NowMicros();
83 std::string old_fname;
84 do {
85 old_fname = OldInfoLogFileName(
86 dbname_, now, db_absolute_path_, db_log_dir_);
87 now++;
88 } while (env_->FileExists(old_fname).ok());
89 env_->RenameFile(log_fname_, old_fname);
90 old_log_files_.push(old_fname);
91 }
92
GetExistingFiles()93 void AutoRollLogger::GetExistingFiles() {
94 {
95 // Empty the queue to avoid duplicated entries in the queue.
96 std::queue<std::string> empty;
97 std::swap(old_log_files_, empty);
98 }
99
100 std::string parent_dir;
101 std::vector<std::string> info_log_files;
102 Status s =
103 GetInfoLogFiles(env_, db_log_dir_, dbname_, &parent_dir, &info_log_files);
104 if (status_.ok()) {
105 status_ = s;
106 }
107 // We need to sort the file before enqueing it so that when we
108 // delete file from the front, it is the oldest file.
109 std::sort(info_log_files.begin(), info_log_files.end());
110
111 for (const std::string& f : info_log_files) {
112 old_log_files_.push(parent_dir + "/" + f);
113 }
114 }
115
TrimOldLogFiles()116 Status AutoRollLogger::TrimOldLogFiles() {
117 // Here we directly list info files and delete them through Env.
118 // The deletion isn't going through DB, so there are shortcomes:
119 // 1. the deletion is not rate limited by SstFileManager
120 // 2. there is a chance that an I/O will be issued here
121 // Since it's going to be complicated to pass DB object down to
122 // here, we take a simple approach to keep the code easier to
123 // maintain.
124
125 // old_log_files_.empty() is helpful for the corner case that
126 // kKeepLogFileNum == 0. We can instead check kKeepLogFileNum != 0 but
127 // it's essentially the same thing, and checking empty before accessing
128 // the queue feels safer.
129 while (!old_log_files_.empty() && old_log_files_.size() >= kKeepLogFileNum) {
130 Status s = env_->DeleteFile(old_log_files_.front());
131 // Remove the file from the tracking anyway. It's possible that
132 // DB cleaned up the old log file, or people cleaned it up manually.
133 old_log_files_.pop();
134 // To make the file really go away, we should sync parent directory.
135 // Since there isn't any consistency issue involved here, skipping
136 // this part to avoid one I/O here.
137 if (!s.ok()) {
138 return s;
139 }
140 }
141 return Status::OK();
142 }
143
ValistToString(const char * format,va_list args) const144 std::string AutoRollLogger::ValistToString(const char* format,
145 va_list args) const {
146 // Any log messages longer than 1024 will get truncated.
147 // The user is responsible for chopping longer messages into multi line log
148 static const int MAXBUFFERSIZE = 1024;
149 char buffer[MAXBUFFERSIZE];
150
151 int count = vsnprintf(buffer, MAXBUFFERSIZE, format, args);
152 (void) count;
153 assert(count >= 0);
154
155 return buffer;
156 }
157
LogInternal(const char * format,...)158 void AutoRollLogger::LogInternal(const char* format, ...) {
159 mutex_.AssertHeld();
160
161 if (!logger_) {
162 return;
163 }
164
165 va_list args;
166 va_start(args, format);
167 logger_->Logv(format, args);
168 va_end(args);
169 }
170
Logv(const char * format,va_list ap)171 void AutoRollLogger::Logv(const char* format, va_list ap) {
172 assert(GetStatus().ok());
173 if (!logger_) {
174 return;
175 }
176
177 std::shared_ptr<Logger> logger;
178 {
179 MutexLock l(&mutex_);
180 if ((kLogFileTimeToRoll > 0 && LogExpired()) ||
181 (kMaxLogFileSize > 0 && logger_->GetLogFileSize() >= kMaxLogFileSize)) {
182 RollLogFile();
183 Status s = ResetLogger();
184 Status s2 = TrimOldLogFiles();
185
186 if (!s.ok()) {
187 // can't really log the error if creating a new LOG file failed
188 return;
189 }
190
191 WriteHeaderInfo();
192
193 if (!s2.ok()) {
194 ROCKS_LOG_WARN(logger.get(), "Fail to trim old info log file: %s",
195 s2.ToString().c_str());
196 }
197 }
198
199 // pin down the current logger_ instance before releasing the mutex.
200 logger = logger_;
201 }
202
203 // Another thread could have put a new Logger instance into logger_ by now.
204 // However, since logger is still hanging on to the previous instance
205 // (reference count is not zero), we don't have to worry about it being
206 // deleted while we are accessing it.
207 // Note that logv itself is not mutex protected to allow maximum concurrency,
208 // as thread safety should have been handled by the underlying logger.
209 logger->Logv(format, ap);
210 }
211
WriteHeaderInfo()212 void AutoRollLogger::WriteHeaderInfo() {
213 mutex_.AssertHeld();
214 for (auto& header : headers_) {
215 LogInternal("%s", header.c_str());
216 }
217 }
218
LogHeader(const char * format,va_list args)219 void AutoRollLogger::LogHeader(const char* format, va_list args) {
220 if (!logger_) {
221 return;
222 }
223
224 // header message are to be retained in memory. Since we cannot make any
225 // assumptions about the data contained in va_list, we will retain them as
226 // strings
227 va_list tmp;
228 va_copy(tmp, args);
229 std::string data = ValistToString(format, tmp);
230 va_end(tmp);
231
232 MutexLock l(&mutex_);
233 headers_.push_back(data);
234
235 // Log the original message to the current log
236 logger_->Logv(format, args);
237 }
238
LogExpired()239 bool AutoRollLogger::LogExpired() {
240 if (cached_now_access_count >= call_NowMicros_every_N_records_) {
241 cached_now = static_cast<uint64_t>(env_->NowMicros() * 1e-6);
242 cached_now_access_count = 0;
243 }
244
245 ++cached_now_access_count;
246 return cached_now >= ctime_ + kLogFileTimeToRoll;
247 }
248 #endif // !ROCKSDB_LITE
249
CreateLoggerFromOptions(const std::string & dbname,const DBOptions & options,std::shared_ptr<Logger> * logger)250 Status CreateLoggerFromOptions(const std::string& dbname,
251 const DBOptions& options,
252 std::shared_ptr<Logger>* logger) {
253 if (options.info_log) {
254 *logger = options.info_log;
255 return Status::OK();
256 }
257
258 Env* env = options.env;
259 std::string db_absolute_path;
260 env->GetAbsolutePath(dbname, &db_absolute_path);
261 std::string fname =
262 InfoLogFileName(dbname, db_absolute_path, options.db_log_dir);
263
264 env->CreateDirIfMissing(dbname); // In case it does not exist
265 // Currently we only support roll by time-to-roll and log size
266 #ifndef ROCKSDB_LITE
267 if (options.log_file_time_to_roll > 0 || options.max_log_file_size > 0) {
268 AutoRollLogger* result = new AutoRollLogger(
269 env, dbname, options.db_log_dir, options.max_log_file_size,
270 options.log_file_time_to_roll, options.keep_log_file_num,
271 options.info_log_level);
272 Status s = result->GetStatus();
273 if (!s.ok()) {
274 delete result;
275 } else {
276 logger->reset(result);
277 }
278 return s;
279 }
280 #endif // !ROCKSDB_LITE
281 // Open a log file in the same directory as the db
282 env->RenameFile(fname,
283 OldInfoLogFileName(dbname, env->NowMicros(), db_absolute_path,
284 options.db_log_dir));
285 auto s = env->NewLogger(fname, logger);
286 if (logger->get() != nullptr) {
287 (*logger)->SetInfoLogLevel(options.info_log_level);
288 }
289 return s;
290 }
291
292 } // namespace ROCKSDB_NAMESPACE
293