Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src/logging.cc: encapsulate log cleaner, match logfile with <base_filename_><date>-<time>.<pid> #502

Merged
merged 4 commits into from
Oct 3, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 166 additions & 90 deletions src/logging.cc
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,31 @@ class LogFileObject : public base::Logger {
bool CreateLogfile(const string& time_pid_string);
};

// Encapsulate all log cleaner related states
class LogCleaner {
public:
LogCleaner();
virtual ~LogCleaner() {}

void Enable(int overdue_days);
void Disable();
void Run(bool base_filename_selected, const string& base_filename) const;

inline bool enabled() const { return enabled_; }

private:
vector<string> GetOverdueLogNames(string log_directory, int days,
const string& base_filename) const;
bool IsLogFromCurrentProject(const string& filepath, const string& base_filename) const;
bool IsLogLastModifiedOver(const string& filepath, int days) const;

bool enabled_;
int overdue_days_;
char dir_delim_; // filepath delimiter ('/' or '\\')
};

LogCleaner log_cleaner;

} // namespace

class LogDestination {
Expand Down Expand Up @@ -867,81 +892,6 @@ void LogDestination::DeleteLogDestinations() {

namespace {

bool IsGlogLog(const string& filename) {
// Check if filename matches the pattern of a glog file:
// "<program name>.<hostname>.<user name>.log...".
const int kKeywordCount = 4;
std::string keywords[kKeywordCount] = {
glog_internal_namespace_::ProgramInvocationShortName(),
LogDestination::hostname(),
MyUserName(),
"log"
};

int start_pos = 0;
for (int i = 0; i < kKeywordCount; i++) {
if (filename.find(keywords[i], start_pos) == filename.npos) {
return false;
}
start_pos += keywords[i].size() + 1;
}
return true;
}

bool LastModifiedOver(const string& filepath, int days) {
// Try to get the last modified time of this file.
struct stat file_stat;

if (stat(filepath.c_str(), &file_stat) == 0) {
// A day is 86400 seconds, so 7 days is 86400 * 7 = 604800 seconds.
time_t last_modified_time = file_stat.st_mtime;
time_t current_time = time(NULL);
return difftime(current_time, last_modified_time) > days * 86400;
}

// If failed to get file stat, don't return true!
return false;
}

vector<string> GetOverdueLogNames(string log_directory, int days) {
// The names of overdue logs.
vector<string> overdue_log_names;

// Try to get all files within log_directory.
DIR *dir;
struct dirent *ent;

char dir_delim = '/';
#ifdef OS_WINDOWS
dir_delim = '\\';
#endif

// If log_directory doesn't end with a slash, append a slash to it.
if (log_directory.at(log_directory.size() - 1) != dir_delim) {
log_directory += dir_delim;
}

if ((dir=opendir(log_directory.c_str()))) {
while ((ent=readdir(dir))) {
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) {
continue;
}
string filepath = log_directory + ent->d_name;
if (IsGlogLog(ent->d_name) && LastModifiedOver(filepath, days)) {
overdue_log_names.push_back(filepath);
}
}
closedir(dir);
}

return overdue_log_names;
}

// Is log_cleaner enabled?
// This option can be enabled by calling google::EnableLogCleaner(days)
bool log_cleaner_enabled_;
int log_cleaner_overdue_days_ = 7;

std::string g_application_fingerprint;

} // namespace
Expand All @@ -963,6 +913,7 @@ string PrettyDuration(int secs) {
return result.str();
}


LogFileObject::LogFileObject(LogSeverity severity,
const char* base_filename)
: base_filename_selected_(base_filename != NULL),
Expand Down Expand Up @@ -1322,16 +1273,147 @@ void LogFileObject::Write(bool force_flush,
}
#endif
// Perform clean up for old logs
if (log_cleaner_enabled_) {
const vector<string>& dirs = GetLoggingDirectories();
for (size_t i = 0; i < dirs.size(); i++) {
vector<string> logs = GetOverdueLogNames(dirs[i], log_cleaner_overdue_days_);
for (size_t j = 0; j < logs.size(); j++) {
static_cast<void>(unlink(logs[j].c_str()));
}
if (log_cleaner.enabled()) {
if (base_filename_selected_ && base_filename_.empty()) {
return;
}
log_cleaner.Run(base_filename_selected_, base_filename_);
}
}
}


LogCleaner::LogCleaner() : enabled_(false), overdue_days_(7), dir_delim_('/') {
#ifdef OS_WINDOWS
dir_delim_ = '\\';
#endif
}

void LogCleaner::Enable(int overdue_days) {
// Setting overdue_days to 0 day should not be allowed!
// Since all logs will be deleted immediately, which will cause troubles.
assert(overdue_days > 0);

enabled_ = true;
overdue_days_ = overdue_days;
}

void LogCleaner::Disable() {
enabled_ = false;
}

void LogCleaner::Run(bool base_filename_selected, const string& base_filename) const {
assert(enabled_ && overdue_days_ > 0);

vector<string> dirs;

if (base_filename_selected) {
string dir = base_filename.substr(0, base_filename.find_last_of(dir_delim_) + 1);
dirs.push_back(dir);
} else {
dirs = GetLoggingDirectories();
}

for (size_t i = 0; i < dirs.size(); i++) {
vector<string> logs = GetOverdueLogNames(dirs[i], overdue_days_, base_filename);
for (size_t j = 0; j < logs.size(); j++) {
static_cast<void>(unlink(logs[j].c_str()));
}
}
}

vector<string> LogCleaner::GetOverdueLogNames(string log_directory, int days,
const string& base_filename) const {
// The names of overdue logs.
vector<string> overdue_log_names;

// Try to get all files within log_directory.
DIR *dir;
struct dirent *ent;

// If log_directory doesn't end with a slash, append a slash to it.
if (log_directory.at(log_directory.size() - 1) != dir_delim_) {
log_directory += dir_delim_;
}

if ((dir=opendir(log_directory.c_str()))) {
while ((ent=readdir(dir))) {
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) {
continue;
}
string filepath = log_directory + ent->d_name;
if (IsLogFromCurrentProject(filepath, base_filename) &&
IsLogLastModifiedOver(filepath, days)) {
overdue_log_names.push_back(filepath);
}
}
closedir(dir);
}

return overdue_log_names;
}

bool LogCleaner::IsLogFromCurrentProject(const string& filepath,
const string& base_filename) const {
// We should remove duplicated delimiters from `base_filename`, e.g.,
// before: "/tmp//<base_filename>.<create_time>.<pid>"
// after: "/tmp/<base_filename>.<create_time>.<pid>"
string cleaned_base_filename;

for (size_t i = 0; i < base_filename.size(); ++i) {
const char& c = base_filename[i];

if (cleaned_base_filename.empty()) {
cleaned_base_filename += c;
} else if (c != dir_delim_ ||
c != cleaned_base_filename.at(cleaned_base_filename.size() - 1)) {
cleaned_base_filename += c;
}
}

// Return early if the filename doesn't start with `cleaned_base_filename`.
if (filepath.find(cleaned_base_filename) != 0) {
return false;
}

// The characters after `cleaned_base_filename` should match the format:
// YYYYMMDD-HHMMSS.pid
for (size_t i = cleaned_base_filename.size(); i < filepath.size(); i++) {
const char& c = filepath[i];

if (i <= cleaned_base_filename.size() + 7) { // 0 ~ 7 : YYYYMMDD
if (c < '0' || c > '9') { return false; }

} else if (i == cleaned_base_filename.size() + 8) { // 8: -
if (c != '-') { return false; }

} else if (i <= cleaned_base_filename.size() + 14) { // 9 ~ 14: HHMMSS
if (c < '0' || c > '9') { return false; }

} else if (i == cleaned_base_filename.size() + 15) { // 15: .
if (c != '.') { return false; }

} else if (i >= cleaned_base_filename.size() + 16) { // 16+: pid
if (c < '0' || c > '9') { return false; }
}
}

return true;
}

bool LogCleaner::IsLogLastModifiedOver(const string& filepath, int days) const {
// Try to get the last modified time of this file.
struct stat file_stat;

if (stat(filepath.c_str(), &file_stat) == 0) {
// A day is 86400 seconds, so 7 days is 86400 * 7 = 604800 seconds.
time_t last_modified_time = file_stat.st_mtime;
time_t current_time = time(NULL);
return difftime(current_time, last_modified_time) > days * 86400;
}

// If failed to get file stat, don't return true!
return false;
}

} // namespace
Expand Down Expand Up @@ -2402,17 +2484,11 @@ void ShutdownGoogleLogging() {
}

void EnableLogCleaner(int overdue_days) {
log_cleaner_enabled_ = true;

// Setting overdue_days to 0 day should not be allowed!
// Since all logs will be deleted immediately, which will cause troubles.
if (overdue_days > 0) {
log_cleaner_overdue_days_ = overdue_days;
}
log_cleaner.Enable(overdue_days);
}

void DisableLogCleaner() {
log_cleaner_enabled_ = false;
log_cleaner.Disable();
}

_END_GOOGLE_NAMESPACE_