Files
front_linux/LFtid1056/cloudfront/code/log4.cpp
2026-01-23 16:10:51 +08:00

724 lines
26 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <ctime>
#include <sstream>
#include <mutex>
#include <thread>
#include <atomic>
#include <cstdio>
#include <unistd.h>
#include <cstring>
#include <sys/stat.h>
#include <sys/types.h>
#include <list>
#include <vector>
#include <array>
#include <fnmatch.h>
#include <unordered_map>
#include <chrono>
//////////////////////////////////////////////////////////////////////////////////////////////////////////
#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_t> 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<std::string, TypedLogger> 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<std::string, LOGEntry> 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<std::string>::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<std::mutex> 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<std::string, RateState> 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();
}
// 前 3 次1 秒一次;第 3 次起300 秒一次,一小时恢复
static bool should_emit(const std::string& key) {
using namespace std::chrono;
const auto now = steady_clock::now();
std::lock_guard<std::mutex> 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<seconds>(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<seconds>(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<std::string, LOGEntry>::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.<id> / monitor.<id>
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<std::mutex> 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<std::string, SendAppender::RateState> 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<std::string, LOGEntry>::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<Layout>(
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<Layout>(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.<mp_id>.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<Layout>(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<Layout>(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<Layout>(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<std::string, TypedLogger>::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