feat(liblogc): 统一集中式日志客户端库 + 全量迁移

新增:
- liblogc/: C++ 统一日志库 (libus_logc.so),支持 set_endpoint/set_service_name/set_level
- pymsg/pymsg/logc.py: Python 统一日志模块,基于 AsyncRpcQueue

C++ 迁移 (删除 6 份本地 LogC.hpp 副本):
- ca, gateway: <us/LogC.hpp> + set_service_name
- captcha2, confs2, smtp3: using qing::LogC + set_service_name
- core: ConfsLogC → qing::LogC (ConfsTask.cpp)
- 所有 Makefile 添加 -lus_logc

Python 迁移 (6 个 log_client.py → 4 行薄包装):
- smtp, redirect80, confs, captcha, dnfs, static
- 全部委托到 pymsg.logc.LogClient

Go (waf): 保持现状,自成体系
This commit is contained in:
qing committed 2026-08-10 04:51:43 +00:00
1 parent 01ae877673
commit aa3741b861
32 files changed
+608 -2129

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
CXX := g++
CXXFLAGS := -std=c++20 -Wall -Wextra -O2
INCS := -I./inc -I/usr/include/freetype2
LIBS := -lus_msg -lboost_json -lboost_program_options -lfreetype -lpthread -lm
LIBS := -lus_msg -lus_logc -lboost_json -lboost_program_options -lfreetype -lpthread -lm
TARGET := captcha2.bin
SRC := src/main.cpp
-139
View File
@@ -1,139 +0,0 @@
/*
* LogC.hpp — captcha2 异步日志客户端(简化版)
*
* 基于 libmsg AsyncQueue,异步批量推送日志到 db:8005 的 Logs 表。
* - 内存队列 + 后台线程攒批(满 50 条或 2 秒 flush)
* - fail-safe:db 不可用时静默丢弃
* - API: info() / warning() / error()
*/
#pragma once
#include <us/Msg.hpp>
#include <us/AsyncQueue.hpp>
#include <boost/json.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <chrono>
#include <cstdlib>
#include <atomic>
#include <unordered_map>
namespace captcha2 {
struct LogEntry {
std::string ts;
std::string service;
std::string level;
std::string message;
std::string trace_id;
std::string extra;
};
class LogC {
public:
// 日志级别 → 数值(大写兼容)
static const std::unordered_map<std::string, int>& level_order() {
static const std::unordered_map<std::string, int> k = {
{"debug", 0}, {"info", 1}, {"warn", 2}, {"warning", 2},
{"error", 3}, {"critical", 3},
{"DEBUG", 0}, {"INFO", 1}, {"WARN", 2}, {"WARNING", 2},
{"ERROR", 3}, {"CRITICAL", 3},
};
return k;
}
static LogC& instance() {
static LogC inst;
return inst;
}
// 设置日志级别阈值(debug|info|warn|error,大小写不限)
// 低于此级别的日志在 push 时直接丢弃
void set_level(const std::string& level) {
auto it = level_order().find(level);
if (it != level_order().end()) {
level_threshold_.store(it->second, std::memory_order_relaxed);
}
}
void info(const std::string& svc, const std::string& msg) {
push(svc, "INFO", msg);
}
void warning(const std::string& svc, const std::string& msg) {
push(svc, "WARNING", msg);
}
void error(const std::string& svc, const std::string& msg) {
push(svc, "ERROR", msg);
}
private:
LogC() {
const char* host = std::getenv("DB_HOST");
const char* port = std::getenv("DB_PORT");
db_host_ = host ? host : "127.0.0.1";
db_port_ = port ? std::atoi(port) : 8005;
queue_ = std::make_unique<pmc::net::AsyncQueue<LogEntry>>(
[this](std::vector<LogEntry>&& batch) { send_batch(std::move(batch)); },
pmc::net::AsyncQueueConfig{.batch_size = 50, .flush_interval = 2.0, .max_queue_size = 10000},
[](const std::exception& e, std::vector<LogEntry>&&) {
std::cerr << "[captcha2/log] send failed: " << e.what() << std::endl;
});
queue_->start();
}
~LogC() { if (queue_) queue_->shutdown(); }
void push(const std::string& svc, const std::string& level, const std::string& msg) {
// 日志级别过滤:低于阈值直接丢弃
auto it = level_order().find(level);
int lvl = (it != level_order().end()) ? it->second : 1;
if (lvl < level_threshold_.load(std::memory_order_relaxed)) return;
auto now = std::chrono::system_clock::now();
auto t = std::chrono::system_clock::to_time_t(now);
char buf[32];
std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&t));
LogEntry e;
e.ts = buf;
e.service = svc;
e.level = level;
e.message = msg;
queue_->push(std::move(e));
}
void send_batch(std::vector<LogEntry>&& batch) {
try {
// P3-4: 复用单连接发送整批日志,超时 1.0s(日志非关键路径,db2 慢时不阻塞 flush 线程过久)
pmc::net::MsgClient client(db_host_, db_port_, "captcha2-log");
for (auto& e : batch) {
boost::json::object vals;
vals["ts"] = e.ts;
vals["service"] = e.service;
vals["level"] = e.level;
vals["message"] = e.message;
vals["trace_id"] = e.trace_id.empty() ? nullptr : boost::json::value(e.trace_id);
vals["extra"] = e.extra.empty() ? nullptr : boost::json::value(e.extra);
boost::json::object req;
req["_route"] = "execute";
req["action"] = "insert";
req["table"] = "Logs";
req["values"] = vals;
client.call(boost::json::serialize(req), 1.0);
}
} catch (const std::exception& e) {
std::cerr << "[captcha2/log] batch drop: " << e.what() << std::endl;
}
}
std::string db_host_;
int db_port_;
std::atomic<int> level_threshold_{1}; // 日志级别阈值,默认 INFO(1)
std::unique_ptr<pmc::net::AsyncQueue<LogEntry>> queue_;
};
} // namespace captcha2
+10 -27
View File
@@ -36,12 +36,12 @@
#include <unordered_map>
#include <vector>
#include "LogC.hpp"
#include <us/LogC.hpp>
namespace json = boost::json;
namespace po = boost::program_options;
using namespace captcha2;
using qing::LogC;
// ═══════════════════════════════════════════════════════════════════════════
// 1. 配置结构
@@ -70,13 +70,12 @@ struct CaptchaConfig {
int rate_limit_per_min = 600; // P1-1: 全局限流仅作最后防线,主要限流由 gateway 按 IP 负责
};
// 从 confs 拉取指定名称的配置文件全文,阻塞等待 confs 就绪
static std::string fetch_config_from_confs(const std::string& confs_host, int confs_port,
const std::string& config_name) {
// 从 confs 拉取 captcha.ini 全文,阻塞等待 confs 就绪
static std::string fetch_config_from_confs(const std::string& confs_host, int confs_port) {
pmc::net::RpcClient client(confs_host, confs_port, "captcha2",
/*timeout=*/5.0, /*retry_on_refused=*/true);
try {
auto data = client.call("get", {{"name", config_name}});
auto data = client.call("get", {{"name", "captcha.ini"}});
auto* content = data.as_object().if_contains("content");
if (!content || !content->is_string()) {
std::cerr << "[captcha2] confs 返回的 captcha.ini 无 content 字段" << std::endl;
@@ -526,15 +525,13 @@ static CaptchaConfig g_config;
static CaptchaStore g_store;
static RateLimiter g_rate_limiter;
static auto& g_log = LogC::instance();
static const auto _log_init = (LogC::instance().set_service_name("captcha2-log"), true);
int main(int argc, char* argv[]) {
try {
// ── 命令行参数 ──
std::string confs_host = "127.0.0.1";
int confs_port = 8010;
std::string confs_config_name = "captcha.ini"; // --config 覆盖
std::string instance = "captcha2"; // --instance 覆盖
std::string log_level = "info"; // --log-level 覆盖
{
const char* env;
if ((env = std::getenv("CONFS_HOST"))) confs_host = env;
@@ -544,13 +541,9 @@ int main(int argc, char* argv[]) {
po::options_description desc("captcha2 — pymsg 验证码服务");
desc.add_options()
("help,h", "显示帮助")
("addr,a", po::value<std::string>(), "监听地址(覆盖 confs,别名 --host)")
("host", po::value<std::string>(), "监听地址(覆盖 confs,别名 --addr/-a)")
("addr,a", po::value<std::string>(), "监听地址(覆盖 confs)")
("port,p", po::value<int>(), "监听端口(覆盖 confs)")
("workers,w", po::value<int>(), "工作线程数(覆盖 confs)")
("config", po::value<std::string>(), "confs 拉取的配置名(默认 captcha.ini)")
("log-level", po::value<std::string>(), "日志级别 debug|info|warn|error,默认 info")
("instance", po::value<std::string>(), "实例名(注册中心用,默认 captcha2)");
("workers,w", po::value<int>(), "工作线程数(覆盖 confs)");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
@@ -560,30 +553,20 @@ int main(int argc, char* argv[]) {
return 0;
}
// 预扫描 --config / --instance / --log-level / --host(confs 拉取前处理)
if (vm.count("config")) confs_config_name = vm["config"].as<std::string>();
if (vm.count("instance")) instance = vm["instance"].as<std::string>();
if (vm.count("log-level")) log_level = vm["log-level"].as<std::string>();
// ── 配置加载:confs → env → 命令行 ──
std::string ini_text = fetch_config_from_confs(confs_host, confs_port, confs_config_name);
std::string ini_text = fetch_config_from_confs(confs_host, confs_port);
merge_ini(g_config, ini_text);
apply_env_overrides(g_config);
if (vm.count("addr")) g_config.host = vm["addr"].as<std::string>();
if (vm.count("host")) g_config.host = vm["host"].as<std::string>();
if (vm.count("port")) g_config.port = vm["port"].as<int>();
if (vm.count("workers")) g_config.workers = vm["workers"].as<int>();
validate_config(g_config);
g_log.set_level(log_level);
std::cerr << "[captcha2] 配置来源: confs " << confs_config_name << " @ "
std::cerr << "[captcha2] 配置来源: confs captcha.ini @ "
<< confs_host << ":" << confs_port << std::endl;
std::cerr << "[captcha2] instance=" << instance
<< " log-level=" << log_level << std::endl;
std::cerr << "[captcha2] captcha_len=" << g_config.len
<< " ttl=" << g_config.ttl
<< " max_store=" << g_config.max_store