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

Add log rotate functionality #174

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
180 changes: 151 additions & 29 deletions loguru.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,30 @@ namespace loguru
{
using namespace std::chrono;

struct FileRotate
{
int log_size_cnt;
int log_size_max;
int log_num_max;
bool is_reopening = false; // to prevent recursive call in file_reopen.
std::string log_path;
std::vector<std::string> log_list;
FILE* file;
std::string mode;
};

#if LOGURU_WITH_FILEABS
struct FileAbs
{
char path[PATH_MAX];
char mode_str[4];
Verbosity verbosity;
struct stat st;
FILE* fp;
bool is_reopening = false; // to prevent recursive call in file_reopen.
FileRotate* file_rotate;
decltype(steady_clock::now()) last_check_time = steady_clock::now();
};
#else
typedef FILE* FileAbs;
typedef FileRotate* FileAbs;
#endif

struct Callback
Expand Down Expand Up @@ -284,16 +295,112 @@ namespace loguru
// ------------------------------------------------------------------------------
#if LOGURU_WITH_FILEABS
void file_reopen(void* user_data);
inline FILE* to_file(void* user_data) { return reinterpret_cast<FileAbs*>(user_data)->fp; }
inline FileRotate* to_file(void* user_data) { return reinterpret_cast<FileAbs*>(user_data)->FileRotate; }
#else
inline FILE* to_file(void* user_data) { return reinterpret_cast<FILE*>(user_data); }
inline FileRotate* to_file(void* user_data) { return reinterpret_cast<FileRotate*>(user_data); }
#endif

static int file_log_list_init(FileRotate* file_rotate)
{
std::string log_file(file_rotate->log_path);

struct stat stat_buffer;
for (int i = 1;; i++) {
std::string log_tmp = log_file + "." + std::to_string(i);
if (stat(log_tmp.c_str(), &stat_buffer) != 0) {
break;
}
if (file_rotate->mode == "a") {
file_rotate->log_list.insert(file_rotate->log_list.begin(), log_tmp);
} else {
std::remove(log_tmp.c_str());
}
}

return 0;
}

static int file_log_init(FileRotate* file_rotate, char* path, int log_size_m, int num)
{
file_rotate->log_path.assign(path);
file_rotate->log_size_max = log_size_m * 1024 * 1024;
file_rotate->log_num_max = num;
file_rotate->is_reopening = false;

// Init log_size_cnt
int num_bytes = 0;
struct stat statbuffer;
if (stat(file_rotate->log_path.c_str(), &statbuffer) == 0) {
num_bytes = statbuffer.st_size;
}
file_rotate->log_size_cnt = num_bytes;

// Init log_list
file_log_list_init(file_rotate);

return 0;
}

static int file_log_rotating_save(FileRotate* file_rotate)
{
std::string log_file(file_rotate->log_path);

// When log file list greater then max number, del the oldest one
while (file_rotate->log_list.size() >= file_rotate->log_num_max) {
std::vector<std::string>::iterator it = file_rotate->log_list.begin();
std::remove((*it).c_str());
file_rotate->log_list.erase(it);
}

// Rename the log file list
for (int i = 0; i < file_rotate->log_list.size(); i++) {
std::string name = log_file + "." + std::to_string(file_rotate->log_list.size() - i + 1);
std::rename(file_rotate->log_list[i].c_str(), name.c_str());
file_rotate->log_list[i] = name;
}

// Rename the new log file
std::string log_file_new = log_file + ".1";
std::rename(log_file.c_str(), log_file_new.c_str());

// Push the new log file to vector
file_rotate->log_list.push_back(log_file_new);

return 0;
}

inline static int file_log_rotate(FileRotate* file_rotate, int len)
{
file_rotate->log_size_cnt += len;
if (file_rotate->log_size_cnt >= file_rotate->log_size_max) {
file_rotate->is_reopening = true;
file_rotate->log_size_cnt = 0;

fflush(file_rotate->file);
fclose(file_rotate->file);
file_log_rotating_save(file_rotate);

#ifdef _WIN32
errno_t file_error = fopen_s(&file_rotate->file, file_rotate->log_path.c_str(), "w");
if (file_error) {
#else
file_rotate->file = fopen(file_rotate->log_path.c_str(), "w");
if (!file_rotate->file) {
#endif
return -1;
}

file_rotate->is_reopening = false;
}

return 0;
}

void file_log(void* user_data, const Message& message)
{
#if LOGURU_WITH_FILEABS
FileAbs* file_abs = reinterpret_cast<FileAbs*>(user_data);
if (file_abs->is_reopening) {
if (file_abs->file_rotate->is_reopening) {
return;
}
// It is better checking file change every minute/hour/day,
Expand All @@ -304,35 +411,43 @@ namespace loguru
file_abs->last_check_time = steady_clock::now();
file_reopen(user_data);
}
FILE* file = to_file(user_data);
if (!file) {
FileRotate* file_rotate = to_file(user_data);
if (!file_rotate) {
return;
}
#else
FILE* file = to_file(user_data);
FileRotate* file_rotate = to_file(user_data);
#endif
fprintf(file, "%s%s%s%s\n",
message.preamble, message.indentation, message.prefix, message.message);
if (g_flush_interval_ms == 0) {
fflush(file);
int len = fprintf(file_rotate->file, "%s%s%s%s\n",
message.preamble, message.indentation, message.prefix, message.message);
file_log_rotate(file_rotate, len);
if (g_flush_interval_ms == 0 && file_rotate->is_reopening == false) {
fflush(file_rotate->file);
}
}

void file_close(void* user_data)
{
FILE* file = to_file(user_data);
if (file) {
fclose(file);
FileRotate* file_rotate = to_file(user_data);
if (file_rotate->file && file_rotate->is_reopening == false) {
fclose(file_rotate->file);
}
#if LOGURU_WITH_FILEABS
delete file_rotate;
delete reinterpret_cast<FileAbs*>(user_data);
#else
delete file_rotate;
#endif
}

void file_flush(void* user_data)
{
FILE* file = to_file(user_data);
fflush(file);
FileRotate* file_rotate = to_file(user_data);
if (file_rotate->is_reopening == true) {
return;
}

fflush(file_rotate->file);
}

#if LOGURU_WITH_FILEABS
Expand All @@ -341,12 +456,12 @@ namespace loguru
FileAbs * file_abs = reinterpret_cast<FileAbs*>(user_data);
struct stat st;
int ret;
if (!file_abs->fp || (ret = stat(file_abs->path, &st)) == -1 || (st.st_ino != file_abs->st.st_ino)) {
file_abs->is_reopening = true;
if (file_abs->fp) {
fclose(file_abs->fp);
if (!file_abs->file_rotate->file || (ret = stat(file_abs->path, &st)) == -1 || (st.st_ino != file_abs->st.st_ino)) {
file_abs->file_rotate->is_reopening = true;
if (file_abs->file_rotate->file) {
fclose(file_abs->file_rotate->file);
}
if (!file_abs->fp) {
if (!file_abs->file_rotate->file) {
VLOG_F(g_internal_verbosity, "Reopening file '" LOGURU_FMT(s) "' due to previous error", file_abs->path);
}
else if (ret < 0) {
Expand All @@ -359,13 +474,13 @@ namespace loguru
if (!create_directories(file_abs->path)) {
LOG_F(ERROR, "Failed to create directories to '" LOGURU_FMT(s) "'", file_abs->path);
}
file_abs->fp = fopen(file_abs->path, file_abs->mode_str);
if (!file_abs->fp) {
file_abs->file_rotate->file = fopen(file_abs->path, file_abs->mode_str);
if (!file_abs->file_rotate->file) {
LOG_F(ERROR, "Failed to open '" LOGURU_FMT(s) "'", file_abs->path);
} else {
stat(file_abs->path, &file_abs->st);
}
file_abs->is_reopening = false;
file_abs->file_rotate->is_reopening = false;
}
}
#endif
Expand Down Expand Up @@ -773,7 +888,7 @@ namespace loguru
free(file_path);
return true;
}
bool add_file(const char* path_in, FileMode mode, Verbosity verbosity)
bool add_file(const char* path_in, FileMode mode, Verbosity verbosity, int log_size_m, int file_num)
{
char path[PATH_MAX];
if (path_in[0] == '~') {
Expand All @@ -800,14 +915,21 @@ namespace loguru
}
#if LOGURU_WITH_FILEABS
FileAbs* file_abs = new FileAbs(); // this is deleted in file_close;
file_abs->file_rotate = new FileRotate(); // this is deleted in file_close;
snprintf(file_abs->path, sizeof(file_abs->path) - 1, "%s", path);
snprintf(file_abs->mode_str, sizeof(file_abs->mode_str) - 1, "%s", mode_str);
stat(file_abs->path, &file_abs->st);
file_abs->fp = file;
file_abs->verbosity = verbosity;
file_abs->file_rotate->file = file;
file_abs->file_rotate->mode.assign(mode_str);
file_log_init(&file_abs->file_rotate, path, log_size_m, file_num);
add_callback(path_in, file_log, file_abs, verbosity, file_close, file_flush);
#else
add_callback(path_in, file_log, file, verbosity, file_close, file_flush);
FileRotate* file_rotate = new FileRotate(); // this is deleted in file_close;
file_rotate->file = file;
file_rotate->mode.assign(mode_str);
file_log_init(file_rotate, path, log_size_m, file_num);
add_callback(path_in, file_log, file_rotate, verbosity, file_close, file_flush);
#endif

if (mode == FileMode::Append) {
Expand Down
4 changes: 3 additions & 1 deletion loguru.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,12 @@ namespace loguru
the given verbosity will be included.
The function will create all directories in 'path' if needed.
If path starts with a ~, it will be replaced with loguru::home_dir()
The log file will be limited by log_size_m and file_num. Eg: log_size_m = 2, file_num = 3 means
that log file max size is 2M and 3 log files will be rotating saved.
To stop the file logging, just call loguru::remove_callback(path) with the same path.
*/
LOGURU_EXPORT
bool add_file(const char* path, FileMode mode, Verbosity verbosity);
bool add_file(const char* path, FileMode mode, Verbosity verbosity, int log_size_m = 2, int file_num = 3);

LOGURU_EXPORT
// Send logs to syslog with LOG_USER facility (see next call)
Expand Down
58 changes: 55 additions & 3 deletions test/loguru_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,53 @@ int main_test(int argc, char* argv[])
return 0;
}

int log_rotate_test(int argc, char* argv[])
{
loguru::init(argc, argv);

loguru::add_file("info.log", loguru::Append, loguru::Verbosity_INFO, 1, 10);
loguru::add_file("max.log", loguru::Truncate, loguru::Verbosity_MAX);

auto a = std::thread([]() {
static int cnt = 0;
loguru::set_thread_name("thread a");
while (cnt < 50000) {
cnt++;
LOG_F(INFO, "a_info_cnt = %d", cnt);
LOG_F(1, "a_1_cnt = %d", cnt);
}
});

auto b = std::thread([]() {
static int cnt = 0;
loguru::set_thread_name("thread b");
while (cnt < 50000) {
cnt++;
LOG_F(INFO, "b_info_cnt = %d", cnt);
LOG_F(1, "b_1_cnt = %d", cnt);
}
});

auto c = std::thread([]() {
static int cnt = 0;
loguru::set_thread_name("thread c");
while (cnt < 50000) {
cnt++;
LOG_F(INFO, "c_info_cnt = %d", cnt);
LOG_F(1, "c_1_cnt = %d", cnt);
}
});

a.join();
b.join();
c.join();

loguru::shutdown();

return 0;
}


void test_SIGSEGV_0()
{
LOG_F(INFO, "Intentionally writing to nullptr:");
Expand Down Expand Up @@ -332,6 +379,11 @@ int main(int argc, char* argv[])
return main_test(argc, argv);
}

if (argc > 2 && argv[2] == std::string("rotate"))
{
return log_rotate_test(argc, argv);
}

loguru::init(argc, argv);

// auto verbose_type_name = loguru::demangle(typeid(std::ofstream).name());
Expand All @@ -341,8 +393,8 @@ int main(int argc, char* argv[])

if (argc == 1)
{
loguru::add_file("latest_readable.log", loguru::Truncate, loguru::Verbosity_INFO);
loguru::add_file("everything.log", loguru::Append, loguru::Verbosity_MAX);
loguru::add_file("latest_readable.log", loguru::Truncate, loguru::Verbosity_INFO, 2, 3);
loguru::add_file("everything.log", loguru::Append, loguru::Verbosity_MAX, 2, 3);
#ifdef LOGURU_SYSLOG
loguru::add_syslog("loguru_test", loguru::Verbosity_MAX);
#endif
Expand Down Expand Up @@ -423,7 +475,7 @@ int main(int argc, char* argv[])
} else if (test == "callback") {
test_log_callback();
} else if (test == "hang") {
loguru::add_file("hang.log", loguru::Truncate, loguru::Verbosity_INFO);
loguru::add_file("hang.log", loguru::Truncate, loguru::Verbosity_INFO, 2, 3);
test_hang_2();
} else {
LOG_F(ERROR, "Unknown test: '%s'", test.c_str());
Expand Down