143 lines
4.1 KiB
C++
143 lines
4.1 KiB
C++
#pragma once
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <filesystem>
|
|
#include <regex>
|
|
#include "utils.h"
|
|
|
|
enum class LogLevel
|
|
{
|
|
INFO,
|
|
WARN,
|
|
ERROR
|
|
};
|
|
|
|
class Logger
|
|
{
|
|
public:
|
|
explicit Logger(const std::string &base_filename, int retention_days = 14)
|
|
: base_filename(base_filename), retention_days(retention_days)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
fs::path log_path(base_filename);
|
|
if (!log_path.parent_path().empty())
|
|
{
|
|
fs::create_directories(log_path.parent_path());
|
|
}
|
|
|
|
current_date = common::get_current_date_string();
|
|
current_log_filename = base_filename + "_" + current_date + ".log";
|
|
open_log_file();
|
|
cleanup_old_logs(); // 初始化时就清理一次
|
|
}
|
|
|
|
void log(LogLevel level, const std::string &msg)
|
|
{
|
|
std::string today = common::get_current_date_string();
|
|
if (today != current_date)
|
|
{
|
|
// 日期切换
|
|
current_date = today;
|
|
current_log_filename = base_filename + "_" + current_date + ".log";
|
|
open_log_file();
|
|
cleanup_old_logs();
|
|
}
|
|
|
|
std::string level_str;
|
|
switch (level)
|
|
{
|
|
case LogLevel::INFO:
|
|
level_str = "[INFO] ";
|
|
break;
|
|
case LogLevel::WARN:
|
|
level_str = "[WARN] ";
|
|
break;
|
|
case LogLevel::ERROR:
|
|
level_str = "[ERROR] ";
|
|
break;
|
|
}
|
|
|
|
std::string full_msg = common::get_current_time_string() + " " + level_str + msg;
|
|
|
|
std::cout << full_msg << std::endl;
|
|
|
|
if (log_file.is_open())
|
|
{
|
|
log_file << full_msg << std::endl;
|
|
log_file.flush();
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::string base_filename; // 基础文件名(不带日期后缀)
|
|
std::string current_log_filename;
|
|
std::string current_date;
|
|
std::ofstream log_file;
|
|
int retention_days;
|
|
|
|
void open_log_file()
|
|
{
|
|
log_file.close();
|
|
log_file.open(current_log_filename, std::ios::app);
|
|
if (!log_file.is_open())
|
|
{
|
|
std::cerr << "[Logger] Failed to open log file: " << current_log_filename << std::endl;
|
|
}
|
|
}
|
|
|
|
void cleanup_old_logs()
|
|
{
|
|
namespace fs = std::filesystem;
|
|
fs::path base_path(base_filename);
|
|
fs::path dir = base_path.parent_path();
|
|
std::string stem = base_path.filename().string();
|
|
|
|
if (dir.empty())
|
|
dir = "."; // 没有指定目录就用当前目录
|
|
|
|
std::regex log_pattern(stem + "_(\\d{4}-\\d{2}-\\d{2})\\.log");
|
|
|
|
auto now = std::chrono::system_clock::now();
|
|
|
|
for (auto &entry : fs::directory_iterator(dir))
|
|
{
|
|
if (!entry.is_regular_file())
|
|
continue;
|
|
std::smatch match;
|
|
std::string fname = entry.path().filename().string();
|
|
|
|
if (std::regex_match(fname, match, log_pattern))
|
|
{
|
|
std::string date_str = match[1];
|
|
std::tm tm = {};
|
|
std::istringstream ss(date_str);
|
|
ss >> std::get_time(&tm, "%Y-%m-%d");
|
|
if (!ss.fail())
|
|
{
|
|
auto file_time = std::chrono::system_clock::from_time_t(std::mktime(&tm));
|
|
auto age_days = std::chrono::duration_cast<std::chrono::hours>(now - file_time).count() / 24;
|
|
if (age_days > retention_days)
|
|
{
|
|
try
|
|
{
|
|
fs::remove(entry.path());
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
std::cerr << "[Logger] Failed to remove old log: " << entry.path()
|
|
<< " (" << e.what() << ")" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// 宏
|
|
#define LOG_INFO(logger, msg) logger.log(LogLevel::INFO, msg)
|
|
#define LOG_WARN(logger, msg) logger.log(LogLevel::WARN, msg)
|
|
#define LOG_ERROR(logger, msg) logger.log(LogLevel::ERROR, msg)
|