#include "Log4CPP.h" #include #include #include #include #include #include #include #include #include #include // 全局模块映射和互斥锁 static std::mutex g_moduleMutex; static std::map g_moduleLoggers; bool LogInstance::parseConfigFile(const std::string& configFile, LogConfig& config) { tinyxml2::XMLDocument doc; if (doc.LoadFile(configFile.c_str()) != tinyxml2::XML_SUCCESS) { std::cerr << "[" << moduleName << "] Load config failed: " << configFile << std::endl; return false; } tinyxml2::XMLElement* root = doc.RootElement(); if (!root) return false; // 先解析全局配置 for (tinyxml2::XMLElement* elem = root->FirstChildElement(); elem; elem = elem->NextSiblingElement()) { const char* name = elem->Name(); const char* value = elem->Attribute("value"); if (!name || !value) continue; // 跳过module元素,稍后处理 if (strcmp(name, "module") == 0) continue; // 全局配置 if (strcmp(name, "root_level") == 0) config.root_level = value; else if (strcmp(name, "log_dir") == 0) config.log_dir = value; else if (strcmp(name, "history_dir") == 0) config.history_dir = value; else if (strcmp(name, "MAX_FILE_SIZE") == 0) { std::string sizeStr = value; if (!sizeStr.empty() && (sizeStr.back() == 'M' || sizeStr.back() == 'm')) { sizeStr.pop_back(); } try { config.max_file_size = std::stoul(sizeStr); } catch (const std::exception& e) { std::cerr << "[" << moduleName << "] Parse MAX_FILE_SIZE failed: " << e.what() << std::endl; config.max_file_size = 10; // 默认值 } } else if (strcmp(name, "MAX_HISTORY") == 0) { try { config.max_backups = std::stoi(value); } catch (const std::exception& e) { std::cerr << "[" << moduleName << "] Parse MAX_HISTORY failed: " << e.what() << std::endl; config.max_backups = 5; // 默认值 } } else if (strcmp(name, "file_pattern") == 0) config.file_pattern = value; else if (strcmp(name, "console_pattern") == 0) config.console_pattern = value; else if (strcmp(name, "log_file_name") == 0) config.log_file_name = value; } // 再查找模块特定配置 for (tinyxml2::XMLElement* elem = root->FirstChildElement(); elem; elem = elem->NextSiblingElement()) { const char* name = elem->Name(); if (!name || strcmp(name, "module") != 0) continue; const char* moduleNameAttr = elem->Attribute("name"); if (!moduleNameAttr || moduleName != moduleNameAttr) continue; // 找到匹配的模块配置 for (tinyxml2::XMLElement* subElem = elem->FirstChildElement(); subElem; subElem = subElem->NextSiblingElement()) { const char* subName = subElem->Name(); const char* subValue = subElem->Attribute("value"); if (!subName || !subValue) continue; if (strcmp(subName, "root_level") == 0) config.root_level = subValue; else if (strcmp(subName, "log_dir") == 0) config.log_dir = subValue; else if (strcmp(subName, "history_dir") == 0) config.history_dir = subValue; else if (strcmp(subName, "MAX_FILE_SIZE") == 0) { std::string sizeStr = subValue; if (!sizeStr.empty() && (sizeStr.back() == 'M' || sizeStr.back() == 'm')) { sizeStr.pop_back(); } try { config.max_file_size = std::stoul(sizeStr); } catch (const std::exception& e) { std::cerr << "[" << moduleName << "] Parse module MAX_FILE_SIZE failed: " << e.what() << std::endl; } } else if (strcmp(subName, "MAX_HISTORY") == 0) { try { config.max_backups = std::stoi(subValue); } catch (const std::exception& e) { std::cerr << "[" << moduleName << "] Parse module MAX_HISTORY failed: " << e.what() << std::endl; } } else if (strcmp(subName, "file_pattern") == 0) config.file_pattern = subValue; else if (strcmp(subName, "console_pattern") == 0) config.console_pattern = subValue; else if (strcmp(subName, "log_file_name") == 0) config.log_file_name = subValue; } break; // 找到匹配模块后跳出循环 } return true; } bool LogInstance::createDirectory(const std::string& dir) { if (dir.empty()) return false; std::string path; for (size_t i = 0; i < dir.length(); ++i) { path += dir[i]; if (dir[i] == '/' || dir[i] == '\\' || i == dir.length() - 1) { // 跳过根目录和空目录名 if (path.length() <= 1) continue; #ifdef _WIN32 if (_mkdir(path.c_str()) != 0 && errno != EEXIST) { std::cerr << "[" << moduleName << "] Create directory failed: " << path << std::endl; return false; } #else if (mkdir(path.c_str(), 0755) != 0 && errno != EEXIST) { std::cerr << "[" << moduleName << "] Create directory failed: " << path << std::endl; return false; } #endif } } return true; } void LogInstance::getCurrentTimeWithMs(std::string& timeStr) { auto now = std::chrono::system_clock::now(); auto now_time_t = std::chrono::system_clock::to_time_t(now); auto now_ms = std::chrono::duration_cast( now.time_since_epoch()) % 1000; tm* now_tm = localtime(&now_time_t); char buffer[80]; strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", now_tm); std::ostringstream oss; oss << buffer << "." << std::setfill('0') << std::setw(3) << now_ms.count(); timeStr = oss.str(); } log4cpp::Priority::PriorityLevel LogInstance::parseLogLevel(const std::string& levelStr) { if (levelStr == "DEBUG") return log4cpp::Priority::DEBUG; if (levelStr == "INFO") return log4cpp::Priority::INFO; if (levelStr == "WARN") return log4cpp::Priority::WARN; if (levelStr == "ERROR") return log4cpp::Priority::ERROR; if (levelStr == "FATAL") return log4cpp::Priority::FATAL; return log4cpp::Priority::DEBUG; // 默认DEBUG } void LogInstance::moveOldLogsToHistory() { // 1. 获取当前日期(保留连字符,与日志文件名格式一致) std::string currentTimeWithMs; getCurrentTimeWithMs(currentTimeWithMs); // 格式:"2025-09-16 10:15:30.123" std::string currentDate; // 提取日期部分(前10个字符,直接保留连字符:"2025-09-16") if (currentTimeWithMs.length() >= 10) { currentDate = currentTimeWithMs.substr(0, 10); // 结果:"2025-09-16" } else { std::cerr << "[" << moduleName << "] 提取当前日期失败" << std::endl; return; } // 2. 构建目录路径 std::string baseLogDir = config_.log_dir + "/" + logHost; std::string historyDir = config_.history_dir + "/" + logHost; if (!createDirectory(historyDir)) { std::cerr << "[" << moduleName << "] 创建历史目录失败: " << historyDir << std::endl; return; } // 3. 遍历日志目录 DIR* dir = opendir(baseLogDir.c_str()); if (!dir) { std::cerr << "[" << moduleName << "] 打开日志目录失败: " << baseLogDir << std::endl; return; } std::string logPrefix = actualLogFileName + "."; // 如 "DevPSGHD." struct dirent* entry; while ((entry = readdir(dir)) != nullptr) { std::string filename = entry->d_name; if (filename == "." || filename == "..") continue; if (filename.find(logPrefix) != 0) continue; // 只处理当前模块的日志 // 4. 提取文件名中的日期(格式:"YYYY-MM-DD") // 文件名结构:logPrefix + [中间部分.] + "YYYY-MM-DD" + ".log" // 修正逻辑:从末尾查找,确保提取最后10位的日期部分 const std::string logSuffix = ".log"; size_t suffixPos = filename.find(logSuffix); if (suffixPos == std::string::npos) continue; // 不是.log文件,跳过 // 日期应该在.log前面,且长度为10(YYYY-MM-DD) size_t dateStart = suffixPos - 10; if (suffixPos < 10) continue; std::string fileDate = filename.substr(dateStart, 10); // 验证日期格式(简单检查:包含两个连字符,且位置正确) if (fileDate.size() != 10 || fileDate[4] != '-' || fileDate[7] != '-') { continue; // 日期格式不正确,跳过 } // 5. 调试输出:打印文件名日期和当前日期(方便确认是否匹配) std::cout << "[" << moduleName << "] 检查日志: " << filename << " | 文件名日期: " << fileDate << " | 当前日期: " << currentDate << std::endl; // 6. 只移动非当天的日志(日期不匹配时) if (fileDate != currentDate) { std::string srcPath = baseLogDir + "/" + filename; std::string destPath = historyDir + "/" + filename; if (std::rename(srcPath.c_str(), destPath.c_str()) != 0) { std::cerr << "[" << moduleName << "] 移动日志失败: " << srcPath << " -> " << destPath << std::endl; } else { std::cout << "[" << moduleName << "] 移动旧日志成功: " << srcPath << " -> " << destPath << std::endl; } } } closedir(dir); } bool LogInstance::init(const std::string& logHostName, const std::string& module, const std::string& configFile, bool toScreen) { if (isInitialized_) { std::cout << "[" << module << "] Log already initialized" << std::endl; return true; } // 初始化基础信息 logHost = logHostName; moduleName = module; outToScreen = toScreen; // 加载配置(优先模块配置,其次全局配置,最后默认) LogConfig tempConfig; if (!configFile.empty()) { if (!parseConfigFile(configFile, tempConfig)) { std::cerr << "[" << module << "] Parse config file failed, using default config" << std::endl; } } config_ = tempConfig; // 确定实际使用的日志文件名 if (config_.log_file_name.empty()) { actualLogFileName = moduleName; // 使用模块名作为日志文件名 } else { actualLogFileName = config_.log_file_name; // 使用配置的日志文件名 // 支持在日志文件名中使用 {host} 占位符 size_t pos = actualLogFileName.find("{host}"); if (pos != std::string::npos) { actualLogFileName.replace(pos, 6, logHost); } } // 创建日志目录 std::string baseLogDir = config_.log_dir + "/" + logHost; if (!createDirectory(baseLogDir)) { std::cerr << "[" << module << "] Create log dir failed: " << baseLogDir << std::endl; return false; } // 生成日志文件路径 std::string timeStr; getCurrentTimeWithMs(timeStr); // 提取年月日部分(前10个字符,格式为"YYYY-MM-DD") std::string dateStr = timeStr.substr(0, 10); // 构造日志路径,仅使用年月日作为时间标识 std::string logPath = baseLogDir + "/" + actualLogFileName + "." + dateStr + ".log"; // 初始化log4cpp组件 try { // 布局 logLayout = new log4cpp::PatternLayout(); logLayout->setConversionPattern(config_.file_pattern); screenLayout = new log4cpp::PatternLayout(); screenLayout->setConversionPattern(config_.console_pattern); // 文件Appender rollLogFile = new log4cpp::RollingFileAppender( "roll_" + moduleName, logPath, config_.max_file_size * 1024 * 1024, // 转换为字节 config_.max_backups, true ); rollLogFile->setLayout(logLayout); // 控制台Appender if (outToScreen) { logScreen = new log4cpp::OstreamAppender("console_" + moduleName, &std::cout); logScreen->setLayout(screenLayout); } // 日志类别(用模块名区分) log4cpp::Category& root = log4cpp::Category::getRoot(); logCat = &root.getInstance("file_" + moduleName); logCat->addAppender(rollLogFile); logCat->setPriority(parseLogLevel(config_.root_level)); if (outToScreen) { coutCat = &root.getInstance("console_" + moduleName); coutCat->addAppender(logScreen); coutCat->setPriority(parseLogLevel(config_.root_level)); } // 移动旧日志 moveOldLogsToHistory(); // 记录初始化日志 std::string initTime; getCurrentTimeWithMs(initTime); logCat->info("Log initialized (file: %s)", logPath.c_str()); if (outToScreen) { coutCat->info("Log initialized (module: %s)", moduleName.c_str()); } isInitialized_ = true; return true; } catch (const std::exception& e) { std::cerr << "[" << module << "] Init failed: " << e.what() << std::endl; destroyLog(); // 清理部分初始化的资源 return false; } } void LogInstance::destroyLog() { if (!isInitialized_) return; std::lock_guard lock(logMtx_); // 记录销毁日志 if (logCat) logCat->info("Log destroying"); // 清理log4cpp资源 if (logCat) logCat->removeAllAppenders(); if (coutCat) coutCat->removeAllAppenders(); delete rollLogFile; rollLogFile = nullptr; delete logScreen; logScreen = nullptr; delete logLayout; logLayout = nullptr; delete screenLayout; screenLayout = nullptr; logCat = nullptr; coutCat = nullptr; isInitialized_ = false; } void LogInstance::setPriority(log4cpp::Priority::PriorityLevel consoleLevel, log4cpp::Priority::PriorityLevel fileLevel) { if (isInitialized_) { if (coutCat) coutCat->setPriority(consoleLevel); if (logCat) logCat->setPriority(fileLevel); } } // 日志模块初始化函数实现 bool initLogModule(const std::string& logHost, const std::string& module, const std::string& configFile, bool toScreen) { std::lock_guard lock(g_moduleMutex); // 检查是否已经初始化 if (g_moduleLoggers.find(module) != g_moduleLoggers.end()) { return true; // 已经初始化 } // 创建新的日志实例 LogInstance* logger = new LogInstance(); bool ret = logger->init(logHost, module, configFile, toScreen); if (ret) { LogManager::getInstance().registerInstance(module, logger); g_moduleLoggers[module] = logger; return true; } else { delete logger; return false; } } // 日志模块销毁函数实现 void destroyLogModule(const std::string& module) { std::lock_guard lock(g_moduleMutex); auto it = g_moduleLoggers.find(module); if (it != g_moduleLoggers.end()) { it->second->destroyLog(); LogManager::getInstance().unregisterInstance(module); delete it->second; g_moduleLoggers.erase(it); } } // 获取所有已初始化的日志模块 std::vector getInitializedLogModules() { std::lock_guard lock(g_moduleMutex); std::vector modules; for (const auto& pair : g_moduleLoggers) { modules.push_back(pair.first); } return modules; } // 销毁所有日志模块 void destroyAllLogModules() { std::lock_guard lock(g_moduleMutex); for (auto& pair : g_moduleLoggers) { pair.second->destroyLog(); LogManager::getInstance().unregisterInstance(pair.first); delete pair.second; } g_moduleLoggers.clear(); }