#pragma once #include #include #include #include #include #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(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)