////////////////////////////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "log4cplus/logger.h" #include "log4cplus/configurator.h" #include "log4cplus/fileappender.h" #include "log4cplus/layout.h" #include "log4cplus/ndc.h" #include "log4cplus/spi/loggingevent.h" #include "rocketmq.h" #include "interface.h" #include "log4.h" ///////////////////////////////////////////////////////////////////////////////////////////////////////////// //log4命名空间 using namespace log4cplus; using namespace log4cplus::helpers; ///////////////////////////////////////////////////////////////////////////////////////////////////////////// //queue结构定义 extern std::mutex queue_data_list_mutex; //queue发送数据锁 extern std::list queue_data_list; //queue发送数据链表 extern unsigned int g_node_id; extern int g_front_seg_index; extern std::string FRONT_INST; extern std::string subdir; //日志主题 extern std::string G_LOG_TOPIC; //////////////////////////////////////////////////////////////////////////////////////////////////// const int LOGTYPE_DEFAULT = LOG_CODE_OTHER; static const int LOGTYPE_WILDCARD = 999; // logtype 通配号码 static const char* ID_WILDCARD = "all"; // id 通配字段 std::map logger_map; DebugSwitch g_debug_switch; //用来控制日志上送的结构 struct LOGEntry { std::string id; //测点和装置需要的id std::string level; // terminal / measurepoint /process int logtype; // 日志类型 int min_grade; // DEBUG / INFO / WARN / ERROR int countdown; // 倒计时,单位秒 }; //日志上送map管理 std::map g_log_entries; pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER; ///////////////////////////////////////////////////////////////////// std::string build_debug_key(const std::string& id, const std::string& level, int logtype); ////////////////////////////////////////////////////////// #if __cplusplus >= 201103L thread_local int g_log_code_tls = 0; #else __thread int g_log_code_tls = 0; #endif ////////////////////////////////////////////////////////辅助函数 // 递归创建目录 bool create_directory_recursive(const std::string& path) { size_t pos = 0; std::string current; while (pos != std::string::npos) { pos = path.find('/', pos + 1); current = path.substr(0, pos); if (!current.empty() && access(current.c_str(), F_OK) != 0) { if (mkdir(current.c_str(), 0755) != 0) { perror(("mkdir failed: " + current).c_str()); return false; } } } return true; } ////////////////////////////////////////////////////////////////////// std::string extract_logger_id(const std::string& logger_name) { size_t pos = logger_name.find('.'); if (pos != std::string::npos && pos + 1 < logger_name.size()) { return logger_name.substr(pos + 1); } return ""; // 没有找到 '.' 或 '.' 后为空 } std::string get_level_str(int level) { switch (level) { case 10000: return "DEBUG"; case 20000: return "NORMAL"; // 或 "INFO" 根据你业务定义 case 30000: return "WARN"; case 40000: return "ERROR"; default: return "UNKNOWN"; } } ////////////////////////////////////////////////////////////////////// TypedLogger::TypedLogger() {} TypedLogger::TypedLogger(const Logger& l, int t) : logger(l), logtype(t) {} DebugSwitch::DebugSwitch() : debug_open(false), min_level(WARN_LOG_LEVEL) {} void DebugSwitch::open() { debug_open = true; } void DebugSwitch::close() { debug_open = false; targets.clear(); type_enable.clear(); } void DebugSwitch::set_target(const std::string& name) { targets.insert(name); } void DebugSwitch::set_level(int level) { min_level = level; } void DebugSwitch::enable_type(int type) { type_enable[type] = true; } void DebugSwitch::disable_type(int type) { type_enable[type] = false; } bool DebugSwitch::match(const std::string& logger_name, int level, int logtype) { if (!debug_open) return false; if (level < min_level) return false; if (type_enable.count(logtype) && !type_enable[logtype]) return false; std::set::iterator it; for (it = targets.begin(); it != targets.end(); ++it) { if (logger_name.find(*it) != std::string::npos) return true; } return false; } /*class SendAppender : public Appender { protected: void append(const spi::InternalLoggingEvent& event) { std::string logger_name = event.getLoggerName(); int level = event.getLogLevel(); std::string msg = event.getMessage(); std::string level_str; if (logger_name.find("process") == 0) level_str = "process"; else if (logger_name.find("monitor") != std::string::npos) level_str = "measurepoint"; else level_str = "terminal"; // ★读取 TLS 中的 code(在打日志的线程里由宏设定) int code = g_log_code_tls; // 若未显式传入,则为 0 if (level == ERROR_LOG_LEVEL || level == WARN_LOG_LEVEL || g_debug_switch.match(logger_name, level, logtype)) { std::ostringstream oss; oss << "{\"processNo\":\"" << std::to_string(g_front_seg_index) << "\",\"nodeId\":\"" << FRONT_INST << "\",\"businessId\":\"" << extract_logger_id(logger_name) << "\",\"level\":\"" << level_str << "\",\"time\":\"" << now_yyyy_mm_dd_hh_mm_ss() << "\",\"grade\":\"" << get_level_str(level) // ★新增:输出 code 字段(整型) << "\",\"code\":\"" << code << "\",\"log\":\"" << escape_json(msg) << "\"}"; std::string jsonString = oss.str(); queue_data_t connect_info; connect_info.strTopic = G_LOG_TOPIC; connect_info.strText = jsonString; connect_info.tag = G_LOG_TAG; connect_info.key = G_LOG_KEY; std::lock_guard lock(queue_data_list_mutex); queue_data_list.push_back(connect_info); } } std::string escape_json(const std::string& input) { std::ostringstream ss; for (unsigned int i = 0; i < input.size(); ++i) { switch (input[i]) { case '\\': ss << "\\\\"; break; case '"': ss << "\\\""; break; case '\n': ss << "\\n"; break; case '\r': ss << "\\r"; break; case '\t': ss << "\\t"; break; default: ss << input[i]; break; } } return ss.str(); } virtual void close() { // 可空实现 } public: SendAppender() {} virtual ~SendAppender() { destructorImpl(); // 重要!释放 log4cplus 基类资源 } };*/ class SendAppender : public Appender { private: struct RateState { uint64_t hit_count = 0; // 同一条日志累计命中次数 std::chrono::steady_clock::time_point last_emit = std::chrono::steady_clock::time_point::min(); }; static std::unordered_map s_rate_map; //频率map static std::mutex s_rate_mutex; // 定义“同一条日志”的规则:logger + level + code + msg //原来只区分了日志登记名和等级,现在具体到每一条日志 static std::string make_key(const std::string& logger_name, int level, int code, const std::string& msg) { std::ostringstream oss; oss << logger_name << "|" << level << "|" << code << "|" << msg; return oss.str(); } // 前 5 次:1 秒一次;第 6 次起:300 秒一次 static bool should_emit(const std::string& key) { using namespace std::chrono; const auto now = steady_clock::now(); std::lock_guard lk(s_rate_mutex); RateState& st = s_rate_map[key]; // 超过恢复时间,重置计数 const int RESET_SEC = 3600; // 一小时 if (st.last_emit != steady_clock::time_point::min()) { auto idle = duration_cast(now - st.last_emit).count(); if (idle >= RESET_SEC) { st.hit_count = 0; // 恢复为“新日志” } } st.hit_count++; const int period_sec = (st.hit_count > 3) ? 300 : 1; if (st.last_emit == steady_clock::time_point::min()) { st.last_emit = now; return true; } const auto elapsed = duration_cast(now - st.last_emit).count(); if (elapsed >= period_sec) { st.last_emit = now; return true; } return false; } static bool find_entry_allow(const std::string& key, int level_val) { //通配方式查找 std::map::iterator it = g_log_entries.find(key); if (it == g_log_entries.end() || it->second.countdown <= 0) return false; return level_val >= it->second.min_grade; } static bool allow_low_level_send(const std::string& id, const std::string& level_str,//层级 int logtype, int level_val) {//告警等级 pthread_mutex_lock(&g_log_mutex); // 1) 精确匹配:id + level + logtype if (find_entry_allow(build_debug_key(id, level_str, logtype), level_val)) { pthread_mutex_unlock(&g_log_mutex); return true; } // 2) logtype 通配:id + level + -1 if (find_entry_allow(build_debug_key(id, level_str, LOGTYPE_WILDCARD), level_val)) { pthread_mutex_unlock(&g_log_mutex); return true; } // 3) id 通配:* + level + logtype if (find_entry_allow(build_debug_key(ID_WILDCARD, level_str, logtype), level_val)) { pthread_mutex_unlock(&g_log_mutex); return true; } // 4) 双通配:* + level + -1 if (find_entry_allow(build_debug_key(ID_WILDCARD, level_str, LOGTYPE_WILDCARD), level_val)) { pthread_mutex_unlock(&g_log_mutex); return true; } pthread_mutex_unlock(&g_log_mutex); return false; } protected: void append(const spi::InternalLoggingEvent& event) override { std::string logger_name = event.getLoggerName(); int level = event.getLogLevel(); std::string msg = event.getMessage(); std::string level_str; if (logger_name.find("process") == 0) level_str = "process"; else if (logger_name.find("monitor") != std::string::npos) level_str = "measurepoint"; else level_str = "terminal"; // TLS code int code = g_log_code_tls; const int safe_logtype = code; // 使用 code 作为 logtype bool allow_send = false; if (level >= WARN_LOG_LEVEL) { allow_send = true; } else { // NORMAL/DEBUG 默认不上送,必须命令打开 std::string ctrl_level = level_str; // "process" / "terminal" / "measurepoint" std::string ctrl_id; if (ctrl_level == "process") { ctrl_id = "process"; // process 用固定 id } else { ctrl_id = extract_logger_id(logger_name); // terminal. / monitor. if (ctrl_id.empty()) { // 没解析出 id,就不给低等级上送(避免误发) allow_send = false; } } if (!ctrl_id.empty()) { allow_send = allow_low_level_send(ctrl_id, ctrl_level, safe_logtype, level); } } if (!allow_send) { return; } // ★新增:限频判断(同一条日志前 5 次 1 秒一次;之后 300 秒一次) const std::string key = make_key(logger_name, level, code, msg); if (!should_emit(key)) { return; } std::ostringstream oss; oss << "{\"processNo\":\"" << std::to_string(g_front_seg_index) << "\",\"nodeId\":\"" << FRONT_INST << "\",\"businessId\":\"" << extract_logger_id(logger_name) << "\",\"level\":\"" << level_str << "\",\"time\":\"" << now_yyyy_mm_dd_hh_mm_ss() << "\",\"grade\":\"" << get_level_str(level) // ★建议:code 用数字(不是字符串) << "\",\"code\":" << code << ",\"log\":\"" << escape_json(msg) << "\"}"; queue_data_t connect_info; connect_info.strTopic = G_LOG_TOPIC; connect_info.strText = oss.str(); connect_info.tag = G_LOG_TAG; connect_info.key = G_LOG_KEY; std::lock_guard lock(queue_data_list_mutex); queue_data_list.push_back(connect_info); } std::string escape_json(const std::string& input) { std::ostringstream ss; for (size_t i = 0; i < input.size(); ++i) { switch (input[i]) { case '\\': ss << "\\\\"; break; case '"': ss << "\\\""; break; case '\n': ss << "\\n"; break; case '\r': ss << "\\r"; break; case '\t': ss << "\\t"; break; default: ss << input[i]; break; } } return ss.str(); } void close() override { // 可空实现 } public: SendAppender() {} virtual ~SendAppender() { destructorImpl(); } }; //用来控制日志上送的静态变量定义 std::unordered_map SendAppender::s_rate_map; std::mutex SendAppender::s_rate_mutex; // 生成唯一 key std::string build_debug_key(const std::string& id, const std::string& level, int logtype) { std::ostringstream oss; oss << id << "|" << level << "|" << logtype; return oss.str(); } // 外部线程中调用:每秒更新所有倒计时,0 则删除 void update_log_entries_countdown() { pthread_mutex_lock(&g_log_mutex); std::map::iterator it = g_log_entries.begin(); while (it != g_log_entries.end()) { if (it->second.countdown > 0) { it->second.countdown--; if (it->second.countdown == 0) { std::cout << "[LOG] debug日志上送自动关闭: " << it->first << std::endl; it = g_log_entries.erase(it); continue; } } ++it; } pthread_mutex_unlock(&g_log_mutex); } void process_log_command(const std::string& id, const std::string& level, const std::string& grade, int logtype) { if (level != "terminal" && level != "measurepoint" && level != "process") return; int grade_level = (grade == "DEBUG") ? DEBUG_LOG_LEVEL : INFO_LOG_LEVEL; std::string key = build_debug_key(id, level, logtype); pthread_mutex_lock(&g_log_mutex); LOGEntry& entry = g_log_entries[key]; // 会自动 insert 或取已有 entry.id = id; entry.level = level; entry.logtype = logtype; entry.min_grade = grade_level; entry.countdown = 60; // 重置倒计时 pthread_mutex_unlock(&g_log_mutex); } Logger init_logger(const std::string& full_name, const std::string& file_dir, const std::string& base_file, SharedAppenderPtr fileAppender) { create_directory_recursive(file_dir); Logger logger = Logger::getInstance(full_name); if (!fileAppender) { std::string file_path = file_dir + "/" + base_file + ".log"; fileAppender = SharedAppenderPtr(new RollingFileAppender(file_path, 1 * 1024 * 1024, 2)); fileAppender->setLayout(std::unique_ptr( new PatternLayout("%D{%Y-%m-%d %H:%M:%S} [%p] [%c] %m%n"))); } SharedAppenderPtr sendAppender(new SendAppender()); logger.addAppender(fileAppender); logger.addAppender(sendAppender); logger.setLogLevel(DEBUG_LOG_LEVEL); return logger; } // 重载版本:无 Appender 传入时调用上面的实现 log4cplus::Logger init_logger(const std::string& full_name, const std::string& file_dir, const std::string& base_file) { return init_logger(full_name, file_dir, base_file, log4cplus::SharedAppenderPtr()); // 空指针 } ////////////////////////////////////////////////////////////////////////////////////////////////////////////应用函数 //进程的日志 void init_logger_process() { std::string base_dir = FRONT_PATH + "/" + subdir + "/processNo" + std::to_string(g_front_seg_index) + "/log"; logger_map["process"] = TypedLogger(init_logger(std::string("process"), base_dir, std::string("process")), LOGTYPE_DEFAULT); std::cout << "process log init ok" << std::endl; } //单个终端的日志初始化 void init_loggers_bydevid(const std::string& dev_id) { if (dev_id.empty()) return; std::string terminal_id = dev_id; std::string base_dir = FRONT_PATH + "/" + subdir + "/processNo" + std::to_string(g_front_seg_index) + "/log"; for (size_t i = 0; i < terminal_devlist.size(); ++i) { terminal_dev& term = terminal_devlist[i]; // 跳过终端台账信息为空的节点 if (term.terminal_id.empty()) { std::cout << "terminal_dev No." << i << " is null" << std::endl; continue; } // 跳过不匹配的终端 if (term.terminal_id != dev_id) continue; std::string ip_str = term.addr_str.empty() ? "unknown" : term.addr_str; std::string device_dir = base_dir + "/" + ip_str; std::string device_key = std::string("terminal.") + dev_id; // 添加判断:终端日志 logger 是否已存在 if (logger_map.find(device_key) == logger_map.end()) { // 所有终端日志(com 和 data)写到同一个 device 日志文件中 std::string file_path_t = device_dir + "/" + dev_id + ".log"; // 共用一个 appender 实例 SharedAppenderPtr device_appender(new RollingFileAppender(file_path_t, 1 * 1024 * 1024, 2)); device_appender->setLayout(std::unique_ptr(new PatternLayout("%D{%Y-%m-%d %H:%M:%S} [%p] [%c] %m%n"))); Logger device_logger = init_logger(device_key, device_dir, dev_id, device_appender); logger_map[device_key] = TypedLogger(device_logger, LOGTYPE_DEFAULT); } // 初始化监测点日志,monitor..COM / .DATA for (size_t j = 0; j < term.line.size(); ++j) { const ledger_monitor& monitor = term.line[j]; if (!monitor.monitor_id.empty()) { std::ostringstream mon_key, mon_path, mon_name; mon_key << "monitor." << monitor.monitor_id; mon_path << device_dir << "/monitor" << j; mon_name << monitor.monitor_id; // 判断监测点 logger 是否已存在 if (logger_map.find(mon_key.str()) == logger_map.end()) { // 所有监测点日志(com 和 data)写到同一个 monitor 日志文件中 std::string file_path_m = mon_path.str() + "/" + mon_name.str() + ".log"; // 共用一个 appender 实例 SharedAppenderPtr monitor_appender(new RollingFileAppender(file_path_m, 1 * 1024 * 1024, 2)); monitor_appender->setLayout(std::unique_ptr(new PatternLayout("%D{%Y-%m-%d %H:%M:%S} [%p] [%c] %m%n"))); Logger mon_logger = init_logger(mon_key.str(), mon_path.str(), mon_name.str(), monitor_appender); logger_map[mon_key.str()] = TypedLogger(mon_logger, LOGTYPE_DEFAULT); } } } break; // 只匹配一个 terminal_id } } //初始化台账日志 void init_loggers() { std::string base_dir = FRONT_PATH + "/" + subdir + "/processNo" + std::to_string(g_front_seg_index) + "/log"; // 遍历所有终端 for (size_t t = 0; t < terminal_devlist.size(); ++t) { terminal_dev& term = terminal_devlist[t]; // 跳过无效终端 if (term.terminal_id.empty()) { std::cout << "terminal_dev No." << t << " is null" << std::endl; continue; } std::string ip_str = term.addr_str.empty() ? "unknown" : term.addr_str; std::string device_dir = base_dir + "/" + ip_str; std::string device_key = std::string("terminal.") + term.terminal_id; // 所有终端日志(com 和 data)写到同一个 device 日志文件中 std::string file_path_t = device_dir + "/" + term.terminal_id + ".log"; // 共用一个 appender 实例 SharedAppenderPtr device_appender(new RollingFileAppender(file_path_t, 1 * 1024 * 1024, 2)); device_appender->setLayout(std::unique_ptr(new PatternLayout("%D{%Y-%m-%d %H:%M:%S} [%p] [%c] %m%n"))); Logger device_logger = init_logger(device_key, device_dir, term.terminal_id, device_appender); logger_map[device_key] = TypedLogger(device_logger, LOGTYPE_DEFAULT); // 初始化监测点日志 for (size_t i = 0; i < term.line.size(); ++i) { const ledger_monitor& monitor = term.line[i]; if (!monitor.monitor_id.empty()) { std::ostringstream mon_key, mon_path, mon_name; mon_key << "monitor." << monitor.monitor_id; mon_path << device_dir << "/monitor" << i; // 用monitor+序号作为目录 mon_name << monitor.monitor_id; std::string file_path_m = mon_path.str() + "/" + mon_name.str() + ".log"; // 共用一个 appender 实例 SharedAppenderPtr monitor_appender(new RollingFileAppender(file_path_m, 1 * 1024 * 1024, 2)); monitor_appender->setLayout(std::unique_ptr(new PatternLayout("%D{%Y-%m-%d %H:%M:%S} [%p] [%c] %m%n"))); Logger mon_logger = init_logger(mon_key.str(), mon_path.str(), mon_name.str(), monitor_appender); logger_map[mon_key.str()] = TypedLogger(mon_logger, LOGTYPE_DEFAULT); } } } } //单个终端的日志删除 void remove_loggers_by_terminal_id(const std::string& terminal_id) { // 遍历所有终端 for (size_t t = 0; t < terminal_devlist.size(); ++t) { terminal_dev& term = terminal_devlist[t]; if (term.terminal_id != terminal_id) continue; // 删除终端日志 logger std::string terminal_key = "terminal." + terminal_id; if (logger_map.count(terminal_key)) { logger_map[terminal_key].logger.removeAllAppenders(); logger_map.erase(terminal_key); } // 删除监测点日志 logger for (size_t i = 0; i < term.line.size(); ++i) { const ledger_monitor& monitor = term.line[i]; if (!monitor.monitor_id.empty()) { std::string mon_prefix = "monitor." + monitor.monitor_id; std::string mon_key = mon_prefix; if (logger_map.count(mon_key)) { logger_map[mon_key].logger.removeAllAppenders(); logger_map.erase(mon_key); } } } std::cout << "[LOG] Logger for terminal_id=" << terminal_id << " removed." << std::endl; break; // 找到匹配终端后退出 } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////封装函数,C/C++通用 #ifdef __cplusplus extern "C" { #endif // 公共函数 void log4_log_with_level(const char* key, const char* msg, int level) { std::map::iterator it = logger_map.find(key); if (it == logger_map.end()) return; Logger logger = it->second.logger; switch (level) { case 0: LOG4CPLUS_DEBUG(logger, msg); break; case 1: LOG4CPLUS_INFO(logger, msg); break; case 2: LOG4CPLUS_WARN(logger, msg); break; case 3: LOG4CPLUS_ERROR(logger, msg); break; default: break; } } // 四个包装函数 void log_debug(const char* key, const char* msg) { log4_log_with_level(key, msg, 0); } void log_info (const char* key, const char* msg) { log4_log_with_level(key, msg, 1); } void log_warn (const char* key, const char* msg) { log4_log_with_level(key, msg, 2); } void log_error(const char* key, const char* msg) { log4_log_with_level(key, msg, 3); } //标准化日志接口 // #define LOGMSG_WITH_TS // 需要时间时再打开 //已在头文件添加编译校验 void format_log_msg(char* buf, size_t buf_size, const char* fmt, ...) { if (!buf || buf_size == 0) return; buf[0] = '\0'; if (!fmt) return; va_list args; va_start(args, fmt); #ifdef LOGMSG_WITH_TS // 写入时间 time_t now = time(NULL); struct tm tm_info; localtime_r(&now, &tm_info); size_t n = strftime(buf, buf_size, "%Y-%m-%d %H:%M:%S ", &tm_info); if (n < buf_size) { vsnprintf(buf + n, buf_size - n, fmt, args); } #else vsnprintf(buf, buf_size, fmt, args); #endif va_end(args); } #ifdef __cplusplus } #endif