2025-06-24 17:55:34 +08:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#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>
|
2026-01-08 15:55:46 +08:00
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
#include <chrono>
|
2026-02-04 17:03:14 +08:00
|
|
|
|
#include <memory>
|
2025-06-24 17:55:34 +08:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#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"
|
2025-09-23 10:19:53 +08:00
|
|
|
|
#include "log4.h"
|
2025-06-24 17:55:34 +08:00
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
//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;
|
2026-05-19 14:07:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 日志限流配置
|
|
|
|
|
|
extern int G_LOG_RATE_RESET_SEC;
|
|
|
|
|
|
extern int G_LOG_RATE_LIMIT_SEC;
|
|
|
|
|
|
extern int G_LOG_RATE_KEEP_ALL_MS;
|
|
|
|
|
|
extern int G_LOG_RATE_KEEP_BURST_MS;
|
|
|
|
|
|
extern int G_LOG_RATE_KEEP_BURST_COUNT;
|
|
|
|
|
|
extern int G_LOG_RATE_KEEP_HIGHFREQ_COUNT;
|
2026-01-08 15:55:46 +08:00
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
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;
|
2026-02-04 17:03:14 +08:00
|
|
|
|
/////////////////////////////////////////////////
|
2026-02-06 15:27:52 +08:00
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
// 原子指针:append 线程只读它,不加锁
|
2026-02-06 15:27:52 +08:00
|
|
|
|
std::shared_ptr<LogLevelCache> g_level_cache_sp;
|
2026-02-04 17:03:14 +08:00
|
|
|
|
///////////////////////////////////////////////////////////////
|
2026-01-08 15:55:46 +08:00
|
|
|
|
//用来控制日志上送的结构
|
|
|
|
|
|
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);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2025-09-10 17:09:12 +08:00
|
|
|
|
//////////////////////////////////////////////////////////
|
2026-01-08 15:55:46 +08:00
|
|
|
|
|
2025-09-10 17:09:12 +08:00
|
|
|
|
#if __cplusplus >= 201103L
|
|
|
|
|
|
thread_local int g_log_code_tls = 0;
|
|
|
|
|
|
#else
|
|
|
|
|
|
__thread int g_log_code_tls = 0;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
////////////////////////////////////////////////////////辅助函数
|
2025-09-10 17:09:12 +08:00
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 递归创建目录
|
2026-04-02 16:23:15 +08:00
|
|
|
|
bool create_directory_recursive(const std::string& path)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (path.empty()) return false;
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
std::string current;
|
2026-04-02 16:23:15 +08:00
|
|
|
|
current.reserve(path.size());
|
|
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < path.size(); ++i) {
|
|
|
|
|
|
current += path[i];
|
|
|
|
|
|
|
|
|
|
|
|
// 遇到 '/' 或最后一个字符时创建
|
|
|
|
|
|
if (path[i] == '/' || i == path.size() - 1) {
|
|
|
|
|
|
if (current.empty()) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 去掉末尾 '/'
|
|
|
|
|
|
std::string dir = current;
|
|
|
|
|
|
if (dir.back() == '/' && dir.size() > 1) {
|
|
|
|
|
|
dir.pop_back();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct stat st;
|
|
|
|
|
|
if (stat(dir.c_str(), &st) != 0) {
|
|
|
|
|
|
if (mkdir(dir.c_str(), 0755) != 0) {
|
|
|
|
|
|
// 如果已经存在(并发场景),忽略
|
|
|
|
|
|
if (errno != EEXIST) {
|
|
|
|
|
|
perror(("mkdir failed: " + dir).c_str());
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
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";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-04 17:03:14 +08:00
|
|
|
|
|
2026-02-06 15:27:52 +08:00
|
|
|
|
const char* loglevel_to_str(int lv) {
|
|
|
|
|
|
switch (lv) {
|
|
|
|
|
|
case DEBUG_LOG_LEVEL: return "DEBUG";
|
|
|
|
|
|
case INFO_LOG_LEVEL: return "NORMAL";
|
|
|
|
|
|
case WARN_LOG_LEVEL: return "WARN";
|
|
|
|
|
|
case ERROR_LOG_LEVEL: return "ERROR";
|
|
|
|
|
|
default: return "UNKNOWN";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
static int str_to_loglevel(const std::string& s, int default_level = WARN_LOG_LEVEL)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (s == "DEBUG") return DEBUG_LOG_LEVEL;
|
|
|
|
|
|
if (s == "NORMAL") return INFO_LOG_LEVEL; // NORMAL 当 INFO
|
|
|
|
|
|
if (s == "INFO") return INFO_LOG_LEVEL;
|
|
|
|
|
|
if (s == "WARN") return WARN_LOG_LEVEL;
|
|
|
|
|
|
if (s == "ERROR") return ERROR_LOG_LEVEL;
|
|
|
|
|
|
return default_level; // 空/非法都兜底
|
|
|
|
|
|
}
|
2025-06-24 17:55:34 +08:00
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-02-04 17:03:14 +08:00
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
static LogLevelCache* build_cache_unlocked()
|
|
|
|
|
|
{
|
|
|
|
|
|
LogLevelCache* nc = new LogLevelCache;
|
|
|
|
|
|
nc->term_min.reserve(terminal_devlist.size());
|
|
|
|
|
|
|
|
|
|
|
|
for (const terminal_dev& t : terminal_devlist) {
|
|
|
|
|
|
const int t_lv = str_to_loglevel(t.DevLogLevel, WARN_LOG_LEVEL);
|
|
|
|
|
|
|
|
|
|
|
|
if (!t.terminal_id.empty())
|
|
|
|
|
|
nc->term_min[t.terminal_id] = t_lv;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
for (const ledger_monitor& m : t.line) {
|
|
|
|
|
|
if (m.monitor_id.empty()) continue;
|
2026-01-08 15:55:46 +08:00
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
// 监测点优先;空/非法自动兜底到终端 t_lv
|
|
|
|
|
|
const int m_lv = str_to_loglevel(m.LineLogLevel, t_lv);
|
|
|
|
|
|
nc->mp_min[m.monitor_id] = m_lv;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nc;
|
|
|
|
|
|
}
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
void refresh_log_level_cache_locked()
|
|
|
|
|
|
{
|
|
|
|
|
|
std::shared_ptr<LogLevelCache> nc(build_cache_unlocked());
|
|
|
|
|
|
std::atomic_store_explicit(&g_level_cache_sp, nc, std::memory_order_release);
|
|
|
|
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-01-07 11:02:03 +08:00
|
|
|
|
class SendAppender : public Appender {
|
|
|
|
|
|
private:
|
2026-05-19 14:07:44 +08:00
|
|
|
|
/*struct RateState {
|
2026-01-07 11:02:03 +08:00
|
|
|
|
uint64_t hit_count = 0; // 同一条日志累计命中次数
|
|
|
|
|
|
std::chrono::steady_clock::time_point last_emit =
|
|
|
|
|
|
std::chrono::steady_clock::time_point::min();
|
2026-05-19 14:07:44 +08:00
|
|
|
|
};*/
|
|
|
|
|
|
struct RateState {
|
|
|
|
|
|
uint64_t pass_count; // 当前周期内已放行条数
|
|
|
|
|
|
uint64_t suppressed_count; // 当前被抑制条数
|
|
|
|
|
|
std::chrono::steady_clock::time_point last_emit;
|
|
|
|
|
|
std::chrono::steady_clock::time_point last_seen;
|
|
|
|
|
|
std::chrono::steady_clock::time_point last_reset;
|
|
|
|
|
|
bool has_emit;
|
|
|
|
|
|
|
|
|
|
|
|
RateState()
|
|
|
|
|
|
: pass_count(0),
|
|
|
|
|
|
suppressed_count(0),
|
|
|
|
|
|
last_emit(),
|
|
|
|
|
|
last_seen(),
|
|
|
|
|
|
last_reset(),
|
|
|
|
|
|
has_emit(false) {}
|
2026-01-07 11:02:03 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
static std::unordered_map<std::string, RateState> s_rate_map; //频率map
|
2026-01-07 11:02:03 +08:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 16:10:51 +08:00
|
|
|
|
// 前 3 次:1 秒一次;第 3 次起:300 秒一次,一小时恢复
|
2026-05-19 14:07:44 +08:00
|
|
|
|
/*static bool should_emit(const std::string& key) {
|
2026-01-07 11:02:03 +08:00
|
|
|
|
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];
|
2026-01-08 15:55:46 +08:00
|
|
|
|
|
|
|
|
|
|
// 超过恢复时间,重置计数
|
|
|
|
|
|
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; // 恢复为“新日志”
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-07 11:02:03 +08:00
|
|
|
|
st.hit_count++;
|
|
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
const int period_sec = (st.hit_count > 3) ? 300 : 1;
|
2026-01-07 11:02:03 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-19 14:07:44 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}*/
|
|
|
|
|
|
static bool should_emit(const std::string& key, uint64_t& suppressed_before_emit) {
|
|
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
|
|
|
|
|
|
|
const auto now = steady_clock::now();
|
|
|
|
|
|
suppressed_before_emit = 0;
|
|
|
|
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lk(s_rate_mutex);
|
|
|
|
|
|
RateState& st = s_rate_map[key];
|
|
|
|
|
|
|
|
|
|
|
|
const int RESET_SEC = G_LOG_RATE_RESET_SEC ; // 1小时重置
|
|
|
|
|
|
const int LIMIT_SEC = G_LOG_RATE_LIMIT_SEC ; // 进入限流后:多久发1条 主要控制中频和高频那些,低频的都直接放行了
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化 / 强制每小时重置
|
|
|
|
|
|
if (st.last_reset.time_since_epoch().count() == 0) {
|
|
|
|
|
|
st.last_reset = now;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
auto since_reset = duration_cast<seconds>(now - st.last_reset).count();
|
|
|
|
|
|
if (since_reset >= RESET_SEC) { //重置周期
|
|
|
|
|
|
st = RateState();
|
|
|
|
|
|
st.last_reset = now;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算当前频率档位(按“本次与上次看到该key的间隔”判断)
|
|
|
|
|
|
// >=60秒/条:全部保留
|
|
|
|
|
|
// [1秒, 60秒):保留前60条,然后1分钟1条
|
|
|
|
|
|
// <1秒:保留前10条,然后1分钟1条
|
|
|
|
|
|
int allow_burst = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (st.last_seen.time_since_epoch().count() == 0) {
|
|
|
|
|
|
// 第一次看到,先按“全部保留”处理
|
|
|
|
|
|
allow_burst = -1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
auto gap_ms = duration_cast<milliseconds>(now - st.last_seen).count();
|
|
|
|
|
|
|
|
|
|
|
|
if (gap_ms >= G_LOG_RATE_KEEP_ALL_MS) { //什么时候不需要限流 //低频 //如果这里设置的很低,就不会限流
|
|
|
|
|
|
allow_burst = -1; // 全部保留
|
|
|
|
|
|
} else if (gap_ms >= G_LOG_RATE_KEEP_BURST_MS) {
|
|
|
|
|
|
allow_burst = G_LOG_RATE_KEEP_BURST_COUNT; // 前60条 //中频 //如果这里设置的比低频低,也不会生效
|
|
|
|
|
|
} else {
|
|
|
|
|
|
allow_burst = G_LOG_RATE_KEEP_HIGHFREQ_COUNT; // 前10条 //高频
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
st.last_seen = now;
|
|
|
|
|
|
|
|
|
|
|
|
// 档位1:全部保留
|
|
|
|
|
|
if (allow_burst == -1) {
|
|
|
|
|
|
suppressed_before_emit = st.suppressed_count;
|
|
|
|
|
|
st.suppressed_count = 0;
|
|
|
|
|
|
st.pass_count++;
|
|
|
|
|
|
st.last_emit = now;
|
|
|
|
|
|
st.has_emit = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 档位2/3:先放前N条
|
|
|
|
|
|
if (st.pass_count < (uint64_t)allow_burst) {
|
|
|
|
|
|
suppressed_before_emit = st.suppressed_count;
|
|
|
|
|
|
st.suppressed_count = 0;
|
|
|
|
|
|
st.pass_count++;
|
|
|
|
|
|
st.last_emit = now;
|
|
|
|
|
|
st.has_emit = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 超过前N条后:进入 1分钟1条
|
|
|
|
|
|
if (!st.has_emit) {
|
|
|
|
|
|
suppressed_before_emit = st.suppressed_count;
|
|
|
|
|
|
st.suppressed_count = 0;
|
|
|
|
|
|
st.pass_count++;
|
|
|
|
|
|
st.last_emit = now;
|
|
|
|
|
|
st.has_emit = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto elapsed = duration_cast<seconds>(now - st.last_emit).count();
|
|
|
|
|
|
if (elapsed >= LIMIT_SEC) {
|
|
|
|
|
|
suppressed_before_emit = st.suppressed_count;
|
|
|
|
|
|
st.suppressed_count = 0;
|
|
|
|
|
|
st.pass_count++;
|
|
|
|
|
|
st.last_emit = now;
|
|
|
|
|
|
st.has_emit = true;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 本条被抑制
|
|
|
|
|
|
st.suppressed_count++;
|
2026-01-07 11:02:03 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
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);
|
|
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
// 1) 精确匹配:id + level + logtype //这个id指定日志种类指定级别的日志
|
2026-01-08 15:55:46 +08:00
|
|
|
|
if (find_entry_allow(build_debug_key(id, level_str, logtype), level_val)) {
|
|
|
|
|
|
pthread_mutex_unlock(&g_log_mutex);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
// 2) logtype 通配:id + level + -1 //这个id指定日志级别的所有日志
|
2026-01-08 15:55:46 +08:00
|
|
|
|
if (find_entry_allow(build_debug_key(id, level_str, LOGTYPE_WILDCARD), level_val)) {
|
|
|
|
|
|
pthread_mutex_unlock(&g_log_mutex);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
// 3) id 通配:* + level + logtype //这个id的指定级别的日志
|
2026-01-08 15:55:46 +08:00
|
|
|
|
if (find_entry_allow(build_debug_key(ID_WILDCARD, level_str, logtype), level_val)) {
|
|
|
|
|
|
pthread_mutex_unlock(&g_log_mutex);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
// 4) 双通配:* + level + -1 //所有指定级别的日志,即根据台账的等级来上送所有日志
|
2026-01-08 15:55:46 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
////////////////////////////////////////////////////////////////添加台账日志控制
|
|
|
|
|
|
|
|
|
|
|
|
static int get_min_send_level_cached(const std::string& level_str, const std::string& logger_name)
|
|
|
|
|
|
{
|
|
|
|
|
|
const int DEFAULT_LEVEL = WARN_LOG_LEVEL;
|
|
|
|
|
|
if (level_str == "process") return DEFAULT_LEVEL;
|
|
|
|
|
|
|
|
|
|
|
|
const std::string id = extract_logger_id(logger_name);
|
|
|
|
|
|
if (id.empty()) return DEFAULT_LEVEL;
|
|
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<LogLevelCache> c =
|
|
|
|
|
|
std::atomic_load_explicit(&g_level_cache_sp, std::memory_order_acquire);
|
|
|
|
|
|
if (!c) return DEFAULT_LEVEL;
|
2026-01-08 15:55:46 +08:00
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
if (level_str == "terminal") {
|
|
|
|
|
|
auto it = c->term_min.find(id);
|
|
|
|
|
|
return (it != c->term_min.end()) ? it->second : DEFAULT_LEVEL;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (level_str == "measurepoint") {
|
|
|
|
|
|
auto it = c->mp_min.find(id);
|
|
|
|
|
|
return (it != c->mp_min.end()) ? it->second : DEFAULT_LEVEL;
|
|
|
|
|
|
}
|
|
|
|
|
|
return DEFAULT_LEVEL;
|
|
|
|
|
|
}
|
2026-01-08 15:55:46 +08:00
|
|
|
|
|
2026-01-07 11:02:03 +08:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
const int safe_logtype = code; // 使用 code 作为 logtype
|
|
|
|
|
|
|
|
|
|
|
|
bool allow_send = false;
|
|
|
|
|
|
|
2026-02-04 17:03:14 +08:00
|
|
|
|
int min_send_level = get_min_send_level_cached(level_str, logger_name);
|
|
|
|
|
|
|
|
|
|
|
|
std::cout << "[LOG] logger: " << logger_name
|
|
|
|
|
|
<< ", level_str: " << level_str
|
|
|
|
|
|
<< ", level: " << level
|
|
|
|
|
|
<< ", min_send_level: " << min_send_level << std::endl;
|
|
|
|
|
|
|
|
|
|
|
|
// ① 高于“台账阈值”的日志:直接上送
|
|
|
|
|
|
if (level >= min_send_level) {
|
2026-01-08 15:55:46 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-07 11:02:03 +08:00
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
if (!allow_send) {
|
2026-01-07 11:02:03 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ★新增:限频判断(同一条日志前 5 次 1 秒一次;之后 300 秒一次)
|
|
|
|
|
|
const std::string key = make_key(logger_name, level, code, msg);
|
2026-05-19 14:07:44 +08:00
|
|
|
|
uint64_t suppressed_before_emit = 0;
|
|
|
|
|
|
if (!should_emit(key, suppressed_before_emit)) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果本次输出前压掉过日志,则在 log 文本后追加统计
|
|
|
|
|
|
std::string final_msg = msg;
|
|
|
|
|
|
if (suppressed_before_emit > 0) {
|
|
|
|
|
|
std::ostringstream suppressed_oss;
|
|
|
|
|
|
suppressed_oss << msg << " 【已过滤重复同类日志 "
|
|
|
|
|
|
<< suppressed_before_emit
|
|
|
|
|
|
<< " 条】";
|
|
|
|
|
|
final_msg = suppressed_oss.str();
|
2026-01-07 11:02:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2026-05-19 14:07:44 +08:00
|
|
|
|
<< ",\"log\":\"" << escape_json(final_msg) << "\"}";
|
2026-01-07 11:02:03 +08:00
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
2025-06-24 17:55:34 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-07 11:02:03 +08:00
|
|
|
|
//用来控制日志上送的静态变量定义
|
|
|
|
|
|
std::unordered_map<std::string, SendAppender::RateState> SendAppender::s_rate_map;
|
|
|
|
|
|
std::mutex SendAppender::s_rate_mutex;
|
|
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
// 生成唯一 key
|
2026-01-08 15:55:46 +08:00
|
|
|
|
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();
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 外部线程中调用:每秒更新所有倒计时,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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
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;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
int grade_level = (grade == "DEBUG") ? DEBUG_LOG_LEVEL : INFO_LOG_LEVEL;
|
|
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
std::string key = build_debug_key(id, level, logtype);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
pthread_mutex_lock(&g_log_mutex);
|
|
|
|
|
|
|
|
|
|
|
|
LOGEntry& entry = g_log_entries[key]; // 会自动 insert 或取已有
|
|
|
|
|
|
entry.id = id;
|
|
|
|
|
|
entry.level = level;
|
2026-01-08 15:55:46 +08:00
|
|
|
|
entry.logtype = logtype;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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";
|
2026-01-08 15:55:46 +08:00
|
|
|
|
logger_map["process"] = TypedLogger(init_logger(std::string("process"), base_dir, std::string("process")), LOGTYPE_DEFAULT);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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;
|
2025-09-22 16:46:33 +08:00
|
|
|
|
std::string device_key = std::string("terminal.") + dev_id;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 添加判断:终端日志 logger 是否已存在
|
2025-09-22 16:46:33 +08:00
|
|
|
|
if (logger_map.find(device_key) == logger_map.end()) {
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-02-06 15:27:52 +08:00
|
|
|
|
// 所有终端日志写到同一个 device 日志文件中
|
2025-06-24 17:55:34 +08:00
|
|
|
|
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")));
|
|
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
Logger device_logger = init_logger(device_key, device_dir, dev_id, device_appender);
|
2026-01-08 15:55:46 +08:00
|
|
|
|
logger_map[device_key] = TypedLogger(device_logger, LOGTYPE_DEFAULT);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-01-06 15:42:31 +08:00
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化监测点日志,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()) {
|
2025-09-22 16:46:33 +08:00
|
|
|
|
std::ostringstream mon_key, mon_path, mon_name;
|
|
|
|
|
|
mon_key << "monitor." << monitor.monitor_id;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
mon_path << device_dir << "/monitor" << j;
|
|
|
|
|
|
mon_name << monitor.monitor_id;
|
|
|
|
|
|
|
|
|
|
|
|
// 判断监测点 logger 是否已存在
|
2025-09-22 16:46:33 +08:00
|
|
|
|
if (logger_map.find(mon_key.str()) == logger_map.end()) {
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 所有监测点日志(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")));
|
|
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
Logger mon_logger = init_logger(mon_key.str(), mon_path.str(), mon_name.str(), monitor_appender);
|
2026-01-08 15:55:46 +08:00
|
|
|
|
logger_map[mon_key.str()] = TypedLogger(mon_logger, LOGTYPE_DEFAULT);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-01-06 15:42:31 +08:00
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
std::string device_key = std::string("terminal.") + term.terminal_id;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 所有终端日志(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")));
|
|
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
Logger device_logger = init_logger(device_key, device_dir, term.terminal_id, device_appender);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
logger_map[device_key] = TypedLogger(device_logger, LOGTYPE_DEFAULT);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-01-06 15:42:31 +08:00
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化监测点日志
|
|
|
|
|
|
for (size_t i = 0; i < term.line.size(); ++i) {
|
|
|
|
|
|
const ledger_monitor& monitor = term.line[i];
|
|
|
|
|
|
|
|
|
|
|
|
if (!monitor.monitor_id.empty()) {
|
2025-09-22 16:46:33 +08:00
|
|
|
|
std::ostringstream mon_key, mon_path, mon_name;
|
|
|
|
|
|
mon_key << "monitor." << monitor.monitor_id;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
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")));
|
|
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
Logger mon_logger = init_logger(mon_key.str(), mon_path.str(), mon_name.str(), monitor_appender);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-01-08 15:55:46 +08:00
|
|
|
|
logger_map[mon_key.str()] = TypedLogger(mon_logger, LOGTYPE_DEFAULT);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2026-01-06 15:42:31 +08:00
|
|
|
|
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//单个终端的日志删除
|
|
|
|
|
|
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
|
2025-09-22 16:46:33 +08:00
|
|
|
|
std::string terminal_key = "terminal." + terminal_id;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
if (logger_map.count(terminal_key)) {
|
|
|
|
|
|
logger_map[terminal_key].logger.removeAllAppenders();
|
|
|
|
|
|
logger_map.erase(terminal_key);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除监测点日志 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;
|
2025-09-22 16:46:33 +08:00
|
|
|
|
std::string mon_key = mon_prefix;
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
2025-09-22 16:46:33 +08:00
|
|
|
|
if (logger_map.count(mon_key)) {
|
|
|
|
|
|
logger_map[mon_key].logger.removeAllAppenders();
|
|
|
|
|
|
logger_map.erase(mon_key);
|
2025-06-24 17:55:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2026-02-06 15:27:52 +08:00
|
|
|
|
if (it == logger_map.end()) {
|
|
|
|
|
|
std::cout << "[LOG][MISS] logger not found, key="
|
|
|
|
|
|
<< (key ? key : "NULL") << std::endl;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-06-24 17:55:34 +08:00
|
|
|
|
|
|
|
|
|
|
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); }
|
|
|
|
|
|
|
|
|
|
|
|
//标准化日志接口
|
2025-09-24 09:44:52 +08:00
|
|
|
|
// #define LOGMSG_WITH_TS // 需要时间时再打开
|
|
|
|
|
|
|
2025-12-23 15:09:28 +08:00
|
|
|
|
//已在头文件添加编译校验
|
2025-06-24 17:55:34 +08:00
|
|
|
|
void format_log_msg(char* buf, size_t buf_size, const char* fmt, ...) {
|
2025-09-24 09:44:52 +08:00
|
|
|
|
if (!buf || buf_size == 0) return;
|
|
|
|
|
|
buf[0] = '\0';
|
|
|
|
|
|
if (!fmt) return;
|
|
|
|
|
|
|
|
|
|
|
|
va_list args;
|
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
|
|
#ifdef LOGMSG_WITH_TS
|
2025-06-24 17:55:34 +08:00
|
|
|
|
// 写入时间
|
|
|
|
|
|
time_t now = time(NULL);
|
|
|
|
|
|
struct tm tm_info;
|
|
|
|
|
|
localtime_r(&now, &tm_info);
|
2025-09-24 09:44:52 +08:00
|
|
|
|
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
|
2025-06-24 17:55:34 +08:00
|
|
|
|
va_end(args);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|