New file |
| | |
| | | #pragma once |
| | | #include "common.h" |
| | | #include <cstring> // ç¨äºstrerror |
| | | #include <cstdio> // ç¨äºstd::rename, std::remove |
| | | #include <utility> // ç¨äºstd::forward |
| | | // æ¥å¿çº§å«æä¸¾ |
| | | enum class LogLevel { |
| | | TRACE, // æè¯¦ç»çè·è¸ªä¿¡æ¯ |
| | | DEBUG, // è°è¯ä¿¡æ¯ |
| | | INFO, // 常è§ä¿¡æ¯ |
| | | WARNING, // è¦åä¿¡æ¯ |
| | | ERRORL, // éè¯¯ä¿¡æ¯ |
| | | FATAL // 严éé误 |
| | | }; |
| | | |
| | | // å°æ¥å¿çº§å«è½¬æ¢ä¸ºå符串 |
| | | inline const char* levelToString(LogLevel level) { |
| | | switch (level) { |
| | | case LogLevel::TRACE: return "TRACE"; |
| | | case LogLevel::DEBUG: return "DEBUG"; |
| | | case LogLevel::INFO: return "INFO"; |
| | | case LogLevel::WARNING: return "WARNING"; |
| | | case LogLevel::ERRORL: return "ERROR"; |
| | | case LogLevel::FATAL: return "FATAL"; |
| | | default: return "UNKNOWN"; |
| | | } |
| | | } |
| | | |
| | | // æ¥å¿é
ç½®ç»æ |
| | | struct LogConfig { |
| | | LogLevel minLevel = LogLevel::INFO; // æå°æ¥å¿çº§å« |
| | | bool consoleOutput = true; // æ¯å¦è¾åºå°æ§å¶å° |
| | | bool fileOutput = false; // æ¯å¦è¾åºå°æä»¶ |
| | | std::string logFilePath = "temp.log";// æ¥å¿æä»¶è·¯å¾ |
| | | bool includeTimestamp = true; // æ¯å¦å
嫿¶é´æ³ |
| | | bool includeThreadId = false; // æ¯å¦å
å«çº¿ç¨ID |
| | | size_t maxFileSize = 10 * 1024 * 1024; // æå¤§æä»¶å¤§å° (10MB) |
| | | int maxBackupFiles = 5; // æå¤§å¤ä»½æä»¶æ° |
| | | }; |
| | | |
| | | class Logger { |
| | | public: |
| | | // è·åæ¥å¿å®ä¾ |
| | | static Logger& instance() { |
| | | static Logger instance; |
| | | return instance; |
| | | } |
| | | |
| | | // é
ç½®æ¥å¿ç³»ç» |
| | | void configure(const LogConfig& config) { |
| | | std::lock_guard<std::mutex> lock(configMutex_); |
| | | config_ = config; |
| | | |
| | | // 妿å¯ç¨æä»¶è¾åºï¼ç¡®ä¿æä»¶å·²æå¼ |
| | | if (config.fileOutput) { |
| | | openLogFile(); |
| | | } |
| | | } |
| | | |
| | | // è®°å½æ¥å¿ (æ¨¡æ¿æ¹æ³ï¼æ¯æä»»ä½å¯æµå¼è¾åºçç±»å) |
| | | template<typename... Args> |
| | | void log(LogLevel level, Args&&... args) { |
| | | if (shutdown_) return; |
| | | |
| | | // å¤å¶é
置以æå°åéèå´ |
| | | LogConfig currentConfig; |
| | | { |
| | | std::lock_guard<std::mutex> lock(configMutex_); |
| | | if (level < config_.minLevel) return; |
| | | currentConfig = config_; |
| | | } |
| | | |
| | | // æé æ¥å¿æ¶æ¯ |
| | | std::ostringstream oss; |
| | | formatLogPrefix(oss, level, currentConfig); |
| | | |
| | | using expander = int[]; |
| | | (void)expander { |
| | | 0, (void(oss << std::forward<Args>(args)), 0)... |
| | | }; |
| | | |
| | | // æ·»å æ¢è¡ç¬¦ |
| | | oss << '\n'; |
| | | std::string message = oss.str(); |
| | | |
| | | // ç´æ¥åå
¥æ¥å¿ |
| | | /*writeLog(message);*/ |
| | | |
| | | // å°æ¥å¿æ¶æ¯å å
¥éå |
| | | { |
| | | std::lock_guard<std::mutex> lock(queueMutex_); |
| | | logQueue_.push(std::move(message)); |
| | | } |
| | | |
| | | // éç¥æ¥å¿çº¿ç¨ææ°æ¶æ¯ |
| | | queueCond_.notify_one(); |
| | | } |
| | | // ä¾¿æ·æ¥å¿æ¹æ³ |
| | | template<typename... Args> void trace(Args&&... args) { |
| | | log(LogLevel::TRACE, std::forward<Args>(args)...); |
| | | } |
| | | |
| | | template<typename... Args> void debug(Args&&... args) { |
| | | log(LogLevel::DEBUG, std::forward<Args>(args)...); |
| | | } |
| | | |
| | | template<typename... Args> void info(Args&&... args) { |
| | | log(LogLevel::INFO, std::forward<Args>(args)...); |
| | | } |
| | | |
| | | template<typename... Args> void warning(Args&&... args) { |
| | | log(LogLevel::WARNING, std::forward<Args>(args)...); |
| | | } |
| | | |
| | | template<typename... Args> void error(Args&&... args) { |
| | | log(LogLevel::ERRORL, std::forward<Args>(args)...); |
| | | } |
| | | |
| | | template<typename... Args> void fatal(Args&&... args) { |
| | | log(LogLevel::FATAL, std::forward<Args>(args)...); |
| | | flush(); |
| | | std::exit(EXIT_FAILURE); |
| | | } |
| | | |
| | | // å·æ°æ¥å¿ç¼å²åº |
| | | void flush() { |
| | | std::lock_guard<std::mutex> lock(fileMutex_); |
| | | if (logFile_.is_open()) { |
| | | logFile_.flush(); |
| | | } |
| | | } |
| | | |
| | | // ææå½æ° |
| | | ~Logger() { |
| | | shutdown(); |
| | | flush(); |
| | | } |
| | | // å®å
¨å
³éæ¥å¿ç³»ç» |
| | | void shutdown() { |
| | | if (shutdown_) return; |
| | | |
| | | shutdown_ = true; |
| | | queueCond_.notify_all(); // å¤é线ç¨ä»¥å¤çéåº |
| | | |
| | | if (workerThread_.joinable()) { |
| | | workerThread_.join(); // çå¾
线ç¨ç»æ |
| | | } |
| | | |
| | | flush(); // æç»å·æ° |
| | | } |
| | | // å 餿·è´æé 彿°åèµå¼è¿ç®ç¬¦ |
| | | Logger(const Logger&) = delete; |
| | | Logger& operator=(const Logger&) = delete; |
| | | |
| | | private: |
| | | LogConfig config_; // æ¥å¿é
ç½® |
| | | std::ofstream logFile_; // æ¥å¿æä»¶æµ |
| | | std::mutex configMutex_; // é
ç½®äºæ¥é |
| | | std::mutex fileMutex_; // æä»¶æä½äºæ¥é |
| | | std::atomic<bool> shutdown_{ false }; // å
³éæ å¿ |
| | | // æ°å¢çº¿ç¨å®å
¨éåç¸å
³æå |
| | | std::queue<std::string> logQueue_; // æ¥å¿æ¶æ¯éå |
| | | std::mutex queueMutex_; // éåäºæ¥é |
| | | std::condition_variable queueCond_; // é忡件åé |
| | | std::thread workerThread_; // æ¥å¿åå
¥çº¿ç¨ |
| | | |
| | | // ç§ææé 彿° |
| | | Logger() { |
| | | // å¯å¨æ¥å¿åå
¥çº¿ç¨ |
| | | workerThread_ = std::thread(&Logger::processLogs, this); |
| | | }; |
| | | // æ¥å¿å¤ç线ç¨å½æ° |
| | | void processLogs() { |
| | | while (true) { |
| | | std::unique_lock<std::mutex> lock(queueMutex_); |
| | | |
| | | // çå¾
æ°æ¥å¿æå
³éä¿¡å· |
| | | queueCond_.wait(lock, [this] { |
| | | return !logQueue_.empty() || shutdown_; |
| | | }); |
| | | |
| | | // å¤çå
³éä¿¡å· |
| | | if (shutdown_ && logQueue_.empty()) { |
| | | break; |
| | | } |
| | | |
| | | // ååºææå¾
å¤çæ¥å¿ |
| | | std::queue<std::string> tempQueue; |
| | | std::swap(logQueue_, tempQueue); |
| | | lock.unlock(); |
| | | |
| | | // å¤çææååºçæ¥å¿ |
| | | while (!tempQueue.empty()) { |
| | | writeLog(tempQueue.front()); |
| | | tempQueue.pop(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥å¿åç¼ |
| | | void formatLogPrefix(std::ostringstream& oss, LogLevel level, const LogConfig& config) { |
| | | // æ¥å¿çº§å«æ ç¾ |
| | | oss << "[" << levelToString(level) << "] "; |
| | | |
| | | // æ¶é´æ³ |
| | | if (config.includeTimestamp) { |
| | | auto now = std::chrono::system_clock::now(); |
| | | auto in_time_t = std::chrono::system_clock::to_time_t(now); |
| | | auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( |
| | | now.time_since_epoch()) % 1000; |
| | | |
| | | std::tm tm; |
| | | #ifdef _WIN32 |
| | | localtime_s(&tm, &in_time_t); |
| | | #else |
| | | localtime_r(&in_time_t, &tm); |
| | | #endif |
| | | |
| | | oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); |
| | | oss << '.' << std::setfill('0') << std::setw(3) << ms.count() << " "; |
| | | } |
| | | |
| | | // 线ç¨ID |
| | | if (config.includeThreadId) { |
| | | oss << "[Thread:" << std::this_thread::get_id() << "] "; |
| | | } |
| | | } |
| | | |
| | | // æå¼æ¥å¿æä»¶ |
| | | void openLogFile() { |
| | | // å
³éå½åæä»¶ï¼å¦æå·²æå¼ï¼ |
| | | if (logFile_.is_open()) { |
| | | logFile_.close(); |
| | | } |
| | | |
| | | // æ£æ¥æä»¶å¤§å°ï¼å¿
è¦æ¶è½®è½¬ |
| | | std::ifstream in(config_.logFilePath, std::ios::binary | std::ios::ate); |
| | | if (in) { |
| | | auto size = in.tellg(); |
| | | if (static_cast<size_t>(size) >= config_.maxFileSize) { |
| | | rotateLogFiles(); |
| | | } |
| | | } |
| | | in.close(); |
| | | |
| | | // æå¼æ°æ¥å¿æä»¶ |
| | | logFile_.open(config_.logFilePath, std::ios::out | std::ios::app); |
| | | if (!logFile_.is_open()) { |
| | | std::cerr << "Failed to open log file: " |
| | | << config_.logFilePath << " - " |
| | | << errno << std::endl; |
| | | } |
| | | } |
| | | |
| | | // 轮转æ¥å¿æä»¶ |
| | | void rotateLogFiles() { |
| | | // å 餿æ§çå¤ä»½æä»¶ |
| | | if (config_.maxBackupFiles > 0) { |
| | | std::string oldestFile = config_.logFilePath + "." + std::to_string(config_.maxBackupFiles); |
| | | std::remove(oldestFile.c_str()); |
| | | |
| | | // éå½åç°æå¤ä»½æä»¶ |
| | | for (int i = config_.maxBackupFiles - 1; i >= 1; i--) { |
| | | std::string oldName = config_.logFilePath + "." + std::to_string(i); |
| | | std::string newName = config_.logFilePath + "." + std::to_string(i + 1); |
| | | |
| | | if (fileExists(oldName)) { |
| | | std::rename(oldName.c_str(), newName.c_str()); |
| | | } |
| | | } |
| | | |
| | | // éå½åå½åæ¥å¿æä»¶ä¸ºå¤ä»½1 |
| | | if (fileExists(config_.logFilePath)) { |
| | | std::string newName = config_.logFilePath + ".1"; |
| | | std::rename(config_.logFilePath.c_str(), newName.c_str()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // æ£æ¥æä»¶æ¯å¦åå¨ |
| | | bool fileExists(const std::string& path) { |
| | | std::ifstream f(path); |
| | | return f.good(); |
| | | } |
| | | |
| | | // å®é
åå
¥æ¥å¿ |
| | | void writeLog(const std::string& message) { |
| | | LogConfig currentConfig; |
| | | { |
| | | std::lock_guard<std::mutex> configLock(configMutex_); |
| | | currentConfig = config_; |
| | | } |
| | | |
| | | // è¾åºå°æ§å¶å° |
| | | if (currentConfig.consoleOutput) { |
| | | if (message.find("[ERROR]") != std::string::npos || |
| | | message.find("[FATAL]") != std::string::npos) { |
| | | std::cerr << message; |
| | | } |
| | | else { |
| | | std::cout << message; |
| | | } |
| | | } |
| | | |
| | | // è¾åºå°æä»¶ |
| | | if (currentConfig.fileOutput) { |
| | | std::lock_guard<std::mutex> fileLock(fileMutex_); |
| | | if (!logFile_.is_open() || !logFile_.good()) { |
| | | openLogFile(); |
| | | } |
| | | |
| | | if (logFile_.good()) { |
| | | logFile_ << message; |
| | | logFile_.flush(); // 宿¶å·æ°å°ç£ç |
| | | |
| | | // æ£æ¥æä»¶å¤§å° |
| | | auto pos = logFile_.tellp(); |
| | | if (static_cast<size_t>(pos) >= currentConfig.maxFileSize) { |
| | | logFile_.close(); |
| | | rotateLogFiles(); |
| | | logFile_.open(currentConfig.logFilePath, std::ios::out | std::ios::app); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // æ¥å¿å®å®ä¹ - æä¾æä»¶ååè¡å·ä¿¡æ¯ |
| | | #define LOG_TRACE(...) Logger::instance().trace("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__) |
| | | #define LOG_DEBUG(...) Logger::instance().debug("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__) |
| | | #define LOG_INFO(...) Logger::instance().info(__VA_ARGS__) |
| | | #define LOG_WARN(...) Logger::instance().warning("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__) |
| | | #define LOG_ERROR(...) Logger::instance().error("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__) |
| | | #define LOG_FATAL(...) Logger::instance().fatal("(", __FILE__, ":", __LINE__, ") ", __VA_ARGS__) |