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