Config option to limit the number of log files
This commit is contained in:
parent
fc65a01a60
commit
894d792e23
2 changed files with 124 additions and 8 deletions
128
LogManager.cpp
128
LogManager.cpp
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "LogManager.h"
|
#include "LogManager.h"
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
@ -18,6 +19,9 @@
|
||||||
|
|
||||||
const char* LogManager::log_codes[] = {"FATAL:", "ERROR:", "Warning:", "Info:", "Verbose:", "Debug:", "Trace:", "Dialog:"};
|
const char* LogManager::log_codes[] = {"FATAL:", "ERROR:", "Warning:", "Info:", "Verbose:", "Debug:", "Trace:", "Dialog:"};
|
||||||
|
|
||||||
|
const char* TimestampPattern = "%04d%02d%02d_%02d%02d%02d";
|
||||||
|
const char* TimestampRegex = "[0-9]{8}_[0-9]{6}"; // relies on the structure of the template above
|
||||||
|
|
||||||
LogManager::LogManager()
|
LogManager::LogManager()
|
||||||
{
|
{
|
||||||
base_clock = std::chrono::steady_clock::now();
|
base_clock = std::chrono::steady_clock::now();
|
||||||
|
|
@ -56,21 +60,41 @@ unsigned int LogManager::getLoglevel()
|
||||||
|
|
||||||
void LogManager::configure(json config, const filesystem::path& defaultDir)
|
void LogManager::configure(json config, const filesystem::path& defaultDir)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> grd(entry_mutex);
|
std::lock_guard<std::recursive_mutex> grd(entry_mutex);
|
||||||
|
|
||||||
/*-------------------------------------------------*\
|
/*-------------------------------------------------*\
|
||||||
| If the log is not open, create a new log file |
|
| If the log is not open, create a new log file |
|
||||||
\*-------------------------------------------------*/
|
\*-------------------------------------------------*/
|
||||||
if(!log_stream.is_open())
|
if(!log_stream.is_open())
|
||||||
{
|
{
|
||||||
|
/*----------------------------------------------------*\
|
||||||
|
| If a limit is declared in the config for the maximum |
|
||||||
|
| number of log files, respect the limit |
|
||||||
|
| Log rotation will remove the files matching the |
|
||||||
|
| current "logfile", starting with the oldest ones |
|
||||||
|
| (according to the timestamp in their filename) |
|
||||||
|
| i.e. with the lexicographically smallest filename |
|
||||||
|
| 0 or less equals no limit (default) |
|
||||||
|
\*----------------------------------------------------*/
|
||||||
|
int loglimit = 0;
|
||||||
|
if(config.contains("file_count_limit") && config["file_count_limit"].is_number_integer())
|
||||||
|
{
|
||||||
|
loglimit = config["file_count_limit"];
|
||||||
|
}
|
||||||
|
|
||||||
if(config.contains("log_file"))
|
if(config.contains("log_file"))
|
||||||
{
|
{
|
||||||
log_file_enabled = config["log_file"];
|
log_file_enabled = config["log_file"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*-----------------------------------------*\
|
||||||
|
| Default template for the logfile name |
|
||||||
|
| The # symbol is replaced with a timestamp |
|
||||||
|
\*-----------------------------------------*/
|
||||||
|
std::string logtempl = "OpenRGB_#.log";
|
||||||
|
|
||||||
if(log_file_enabled)
|
if(log_file_enabled)
|
||||||
{
|
{
|
||||||
std::string logname = "OpenRGB_#.log";
|
|
||||||
|
|
||||||
/*-------------------------------------------------*\
|
/*-------------------------------------------------*\
|
||||||
| If the logfile is defined in the configuration, |
|
| If the logfile is defined in the configuration, |
|
||||||
|
|
@ -84,11 +108,10 @@ void LogManager::configure(json config, const filesystem::path& defaultDir)
|
||||||
std::string tmpname = config["logfile"];
|
std::string tmpname = config["logfile"];
|
||||||
if(!tmpname.empty())
|
if(!tmpname.empty())
|
||||||
{
|
{
|
||||||
logname = tmpname;
|
logtempl = tmpname;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-------------------------------------------------*\
|
/*-------------------------------------------------*\
|
||||||
| If the # symbol is found in the log file name, |
|
| If the # symbol is found in the log file name, |
|
||||||
| replace it with a timestamp |
|
| replace it with a timestamp |
|
||||||
|
|
@ -96,8 +119,9 @@ void LogManager::configure(json config, const filesystem::path& defaultDir)
|
||||||
time_t t = time(0);
|
time_t t = time(0);
|
||||||
struct tm* tmp = localtime(&t);
|
struct tm* tmp = localtime(&t);
|
||||||
char time_string[64];
|
char time_string[64];
|
||||||
snprintf(time_string, 64, "%04d%02d%02d_%02d%02d%02d", 1900 + tmp->tm_year, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
|
snprintf(time_string, 64, TimestampPattern, 1900 + tmp->tm_year, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
|
||||||
|
|
||||||
|
std::string logname = logtempl;
|
||||||
size_t oct = logname.find("#");
|
size_t oct = logname.find("#");
|
||||||
if(oct != logname.npos)
|
if(oct != logname.npos)
|
||||||
{
|
{
|
||||||
|
|
@ -114,6 +138,12 @@ void LogManager::configure(json config, const filesystem::path& defaultDir)
|
||||||
}
|
}
|
||||||
filesystem::create_directories(p.parent_path());
|
filesystem::create_directories(p.parent_path());
|
||||||
|
|
||||||
|
/*----------------------------------------------*\
|
||||||
|
| "Log rotation": remove old log files exceeding |
|
||||||
|
| the current configured limit |
|
||||||
|
\*----------------------------------------------*/
|
||||||
|
rotate_logs(p.parent_path(), filesystem::u8path(logtempl).filename(), loglimit);
|
||||||
|
|
||||||
/*-------------------------------------------------*\
|
/*-------------------------------------------------*\
|
||||||
| Open the logfile |
|
| Open the logfile |
|
||||||
\*-------------------------------------------------*/
|
\*-------------------------------------------------*/
|
||||||
|
|
@ -200,7 +230,7 @@ void LogManager::_flush()
|
||||||
|
|
||||||
void LogManager::flush()
|
void LogManager::flush()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> grd(entry_mutex);
|
std::lock_guard<std::recursive_mutex> grd(entry_mutex);
|
||||||
_flush();
|
_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,7 +328,7 @@ void LogManager::append(const char* filename, int line, unsigned int level, cons
|
||||||
va_list va;
|
va_list va;
|
||||||
va_start(va, fmt);
|
va_start(va, fmt);
|
||||||
|
|
||||||
std::lock_guard<std::mutex> grd(entry_mutex);
|
std::lock_guard<std::recursive_mutex> grd(entry_mutex);
|
||||||
_append(filename, line, level, fmt, va);
|
_append(filename, line, level, fmt, va);
|
||||||
|
|
||||||
va_end(va);
|
va_end(va);
|
||||||
|
|
@ -367,3 +397,87 @@ void LogManager::UnregisterDialogShowCallback(LogDialogShowCallback callback, vo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogManager::rotate_logs(const filesystem::path& folder, const filesystem::path& templ, int max_count)
|
||||||
|
{
|
||||||
|
if(max_count < 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string templ2 = templ.filename().generic_u8string();
|
||||||
|
|
||||||
|
// Process the templ2 into a usable regex
|
||||||
|
// The # symbol is replaced with a timestamp regex
|
||||||
|
// Any regex-unfriendly symbols are escaped with a backslash
|
||||||
|
std::string regex_templ = "^";
|
||||||
|
for(size_t i = 0; i < templ2.size(); ++i)
|
||||||
|
{
|
||||||
|
switch(templ2[i])
|
||||||
|
{
|
||||||
|
// Symbols that have special meanings in regex'es need backslash escaping
|
||||||
|
case '.':
|
||||||
|
case '^':
|
||||||
|
case '$':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '{':
|
||||||
|
case '}':
|
||||||
|
case '+':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '*':
|
||||||
|
case '-':
|
||||||
|
case '\\': // Should have been filtered out by the filesystem processing, but... who knows
|
||||||
|
regex_templ.push_back('\\');
|
||||||
|
regex_templ.push_back(templ2[i]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// The # symbol is reserved for the timestamp and thus is replaced with the timestamp regex template
|
||||||
|
case '#':
|
||||||
|
regex_templ.append(TimestampRegex);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
regex_templ.push_back(templ2[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regex_templ.push_back('$');
|
||||||
|
|
||||||
|
std::regex r(regex_templ);
|
||||||
|
|
||||||
|
std::vector<filesystem::path> valid_paths;
|
||||||
|
std::filesystem::directory_iterator it(folder);
|
||||||
|
for(; it != filesystem::end(it); ++it)
|
||||||
|
{
|
||||||
|
if(it->is_regular_file())
|
||||||
|
{
|
||||||
|
std::string fname = it->path().filename().u8string();
|
||||||
|
if(std::regex_match(fname, r))
|
||||||
|
{
|
||||||
|
valid_paths.push_back(it->path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(valid_paths.begin(), valid_paths.end());
|
||||||
|
|
||||||
|
size_t remove_count = valid_paths.size() - max_count + 1; // NOTE: the "1" extra file to remove creates space for the one we're about to create
|
||||||
|
if(remove_count > valid_paths.size()) // for max_count <= 0 and to prevent any possible errors in the above logic
|
||||||
|
{
|
||||||
|
remove_count = valid_paths.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < remove_count; ++i)
|
||||||
|
{
|
||||||
|
std::error_code ec; // Uses error code to force the `remove` call to be `noexcept`
|
||||||
|
if(filesystem::remove(valid_paths[i], ec))
|
||||||
|
{
|
||||||
|
LOG_VERBOSE("[LogManager] Removed log file [%s] during rotation", valid_paths[i].u8string().c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_WARNING("[LogManager] Failed to remove log file [%s] during rotation: %s", valid_paths[i].u8string().c_str(), ec.message().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ private:
|
||||||
LogManager(const LogManager&) = delete;
|
LogManager(const LogManager&) = delete;
|
||||||
LogManager(LogManager&&) = delete;
|
LogManager(LogManager&&) = delete;
|
||||||
~LogManager();
|
~LogManager();
|
||||||
std::mutex entry_mutex;
|
std::recursive_mutex entry_mutex;
|
||||||
std::mutex section_mutex;
|
std::mutex section_mutex;
|
||||||
std::ofstream log_stream;
|
std::ofstream log_stream;
|
||||||
|
|
||||||
|
|
@ -91,6 +91,8 @@ private:
|
||||||
// A non-guarded flush()
|
// A non-guarded flush()
|
||||||
void _flush();
|
void _flush();
|
||||||
|
|
||||||
|
void rotate_logs(const filesystem::path& folder, const filesystem::path& templ, int max_count);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static LogManager* get();
|
static LogManager* get();
|
||||||
void configure(json config, const filesystem::path & defaultDir);
|
void configure(json config, const filesystem::path & defaultDir);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue