microLog 后端开发指南
📅 2026/7/5 6:57:06
👁️ 阅读次数
📝 编程学习
microLog 后端开发指南
概述
microLog 采用抽象工厂模式与门面模式进行架构设计,能够方便地扩展新的日志后端。本文档将逐步说明如何开发自定义后端实现,并提供完整的示例与最佳实践。
文中示例代码仅为展示原理,并没有进行编译测试
架构设计
核心接口
logItfc 基类
所有后端必须继承logItfc并实现以下纯虚函数:
classlogItfc{public:enumclasslevel_t{DEBUG=0,INFO=1,WARN=2,ERROR=3};// 必须实现:日志记录核心方法virtualvoidvlog(level_t level,constchar*fmt,va_list args)=0;// 必须实现:获取文件描述符(无则返回 -1)virtualintget_fd__()const=0;// 必须实现:检查后端是否有效virtualboolis_valid__()const=0;protected:// 可用的工具方法constchar*level_2_string__(level_t level);// 级别转字符串std::stringnowStr();// 获取当前时间字符串std::shared_ptr<wheels::threadPool>pt_runner__;// 线程池指针};开发步骤
步骤 1:定义后端枚举类型
在include/log.hpp的tinyLogType枚举中添加新类型:
enumclasstinyLogType{UDP=0,LOCAL=1,PIPE=2,UNIXSOCK=3,TERMINAL=4,TCP=5,SQLITE3=6,MYSQL=7,POSTGRESQL=8,CUSTOM=9,// 添加新类型,按顺序递增};步骤 2:创建后端头文件
创建include/logDetail/custom_log.hpp:
#pragmaonce#include<string>#include<cstdint>#include"logDetail/itfc.hpp"namespacemicroLog{classcustomLog:publiclogItfc{private:// 自定义成员变量std::string m_config__;boolm_inited__;public:// 构造函数:接收自定义参数customLog(conststd::string&config);~customLog();// 必须实现的接口virtualintget_fd__()constoverride;virtualboolis_valid__()constoverride;virtualvoidvlog(level_t level,constchar*fmt,va_list args)override;// 自定义方法(可选)boolinit();};}// namespace microLog步骤 3:实现后端
创建src/logDetail/custom_log.cpp:
#include<cstdarg>#include<cstdio>#include"logDetail/custom_log.hpp"namespacemicroLog{customLog::customLog(conststd::string&config):m_config__(config),m_inited__(false){if(!init()){throwstd::runtime_error("customLog 初始化失败");}}customLog::~customLog(){// 清理资源}boolcustomLog::init(){// 初始化逻辑:连接资源、创建对象等m_inited__=true;returntrue;}intcustomLog::get_fd__()const{// 如果后端使用文件描述符,返回 fd;否则返回 -1return-1;}boolcustomLog::is_valid__()const{// 返回后端是否有效returnm_inited__;}voidcustomLog::vlog(level_t level,constchar*fmt,va_list args){if(!is_valid__()){return;}// 1. 获取时间戳std::string ts=nowStr();// 2. 获取级别字符串constchar*level_str=level_2_string__(level);// 3. 格式化日志内容charbuf[4096]={0};va_list args_copy;va_copy(args_copy,args);vsnprintf(buf,sizeof(buf),fmt,args_copy);va_end(args_copy);// 4. 输出到自定义目标(示例:控制台)printf("[%s] [%s] %s\n",ts.c_str(),level_str,buf);}}// namespace microLog步骤 4:注册到抽象工厂
在include/log.hpp的logFactoryHelper中添加新后端:
#include"logDetail/custom_log.hpp"// 添加头文件namespacemicroLog{usinglogFactoryHelper=wheels::dm::abstractFactory<LOG<udpLog>,LOG<localLog>,LOG<pipeLog>,LOG<unixSock>,LOG<terminal>,LOG<tcpLog>,LOG<sqlite3Log>,LOG<mysqlLog>,LOG<postgresqlLog>,LOG<customLog>// 添加新后端>;}步骤 5:配置 CMakeLists.txt
在src/logDetail/CMakeLists.txt中添加新源文件:
set(LOG_DETAIL_SRC ... custom_log.cpp ... )如果新后端依赖外部库,需添加相应的find_package和target_link_libraries。
使用示例
#include"log.hpp"// 创建自定义后端实例autolog=createLog<tinyLogType::CUSTOM>(4,"my_config");// 使用日志log->info("系统启动");log->debug("调试信息: %d",42);log->warn("警告: 资源不足");log->error("错误: %s","连接失败");完整示例:Redis 后端
头文件redis_log.hpp
#pragmaonce#include<string>#include<cstdint>#include"logDetail/itfc.hpp"#ifdefHAVE_REDIS#include<hiredis/hiredis.h>#endifnamespacemicroLog{classredisLog:publiclogItfc{private:#ifdefHAVE_REDISredisContext*m_ctx__;#endifstd::string m_host__;uint16_tm_port__;std::string m_key__;boolm_inited__;public:redisLog(conststd::string&host="localhost",uint16_tport=6379,conststd::string&key="microLog");~redisLog();virtualintget_fd__()constoverride;virtualboolis_valid__()constoverride;virtualvoidvlog(level_t level,constchar*fmt,va_list args)override;};}// namespace microLog实现文件redis_log.cpp
#include<cstdarg>#include<cstdio>#include<string>#include"logDetail/redis_log.hpp"namespacemicroLog{redisLog::redisLog(conststd::string&host,uint16_tport,conststd::string&key):m_host__(host),m_port__(port),m_key__(key),m_inited__(false){#ifdefHAVE_REDISm_ctx__=redisConnect(host.c_str(),port);if(!m_ctx__||m_ctx__->err){if(m_ctx__){std::cerr<<"Redis 连接失败: "<<m_ctx__->errstr<<std::endl;redisFree(m_ctx__);m_ctx__=nullptr;}else{std::cerr<<"Redis 连接失败: 内存分配错误"<<std::endl;}return;}m_inited__=true;#elsethrowstd::runtime_error("Redis 支持未启用");#endif}redisLog::~redisLog(){#ifdefHAVE_REDISif(m_ctx__){redisFree(m_ctx__);m_ctx__=nullptr;}#endif}intredisLog::get_fd__()const{return-1;}boolredisLog::is_valid__()const{#ifdefHAVE_REDISreturnm_inited__&&m_ctx__&&!m_ctx__->err;#elsereturnfalse;#endif}voidredisLog::vlog(level_t level,constchar*fmt,va_list args){#ifdefHAVE_REDISif(!is_valid__()){return;}charbuf[4096]={0};std::string ts=nowStr();snprintf(buf,sizeof(buf),"[%s] [%s] ",ts.c_str(),level_2_string__(level));charmsg[4096]={0};va_list args_copy;va_copy(args_copy,args);vsnprintf(msg,sizeof(msg),fmt,args_copy);va_end(args_copy);std::string log_entry=std::string(buf)+msg;redisReply*reply=(redisReply*)redisCommand(m_ctx__,"LPUSH %s %s",m_key__.c_str(),log_entry.c_str());if(reply){freeReplyObject(reply);}#endif}}// namespace microLog最佳实践
1. 线程安全
若后端包含共享状态,应使用互斥锁保护:
classcustomLog:publiclogItfc{private:std::mutex m_mutex__;// ...voidvlog(level_t level,constchar*fmt,va_list args){std::lock_guard<std::mutex>lock(m_mutex__);// ...}};2. 资源管理
在析构函数中正确释放资源:
customLog::~customLog(){close(m_fd__);deletem_object__;// ...}3. 错误处理
使用异常或返回值处理初始化失败:
customLog::customLog(){if(!init()){throwstd::runtime_error("customLog 初始化失败");}}4. 条件编译
对于依赖外部库的后端,使用条件编译:
#ifdefHAVE_REDIS#include<hiredis/hiredis.h>#endifvoidvlog(...){#ifdefHAVE_REDIS// 实现逻辑#endif}5. 批量写入优化
对于网络或数据库后端,建议实现批量提交:
classcustomLog:publiclogItfc{private:std::vector<std::string>m_buffer__;size_t m_batch_size__;std::mutex m_mutex__;voidflush(){// 批量提交所有缓冲数据}voidvlog(...){{std::lock_guard<std::mutex>lock(m_mutex__);m_buffer__.push_back(entry);if(m_buffer__.size()>=m_batch_size__){flush();}}}};6. 连接重连
对于网络后端,实现自动重连机制:
boolcustomLog::check_connection(){if(!m_connected__){reconnect();}returnm_connected__;}voidvlog(...){if(!check_connection()){return;}// 写入逻辑}调试技巧
1. 验证编译
mkdir-pbuild&&cdbuild cmake..-DBUILD_TESTS=ONmake-j42. 添加测试
创建test/test_custom.cpp:
#include"log.hpp"intmain(){autolog=createLog<tinyLogType::CUSTOM>(4,"test_config");if(!log){std::cerr<<"创建失败"<<std::endl;return1;}log->info("测试信息");log->debug("测试调试");log->warn("测试警告");log->error("测试错误");std::cout<<"测试通过"<<std::endl;return0;}3. 启用调试模式
cmake..-DBUILD_DEBUG=ON已有后端参考
| 后端 | 文件路径 | 特点 |
|---|---|---|
| LOCAL | logDetail/local.hpp | 环形文件,嵌入式友好 |
| UDP | logDetail/udp.hpp | 无连接,高性能 |
| TCP | logDetail/tcp.hpp | 可靠传输 |
| PIPE | logDetail/pipe.hpp | 进程间通信 |
| UNIXSOCK | logDetail/unixSock.hpp | 本地套接字 |
| TERMINAL | logDetail/terminal.hpp | 控制台输出,彩色支持 |
| SQLITE3 | logDetail/sqlite3_log.hpp | 嵌入式数据库 |
| MYSQL | logDetail/mysql_log.hpp | 双连接 + 事务批量提交 |
| POSTGRESQL | logDetail/postgresql_log.hpp | 双连接 + 事务批量提交 |
常见问题
Q1: 如何处理阻塞操作?
A: 在vlog中不要执行长时间阻塞操作。如果必须阻塞,可考虑使用异步线程处理。
Q2: 是否需要继承std::enable_shared_from_this?
A: 若需要在回调中获取自身的shared_ptr,建议继承:
classcustomLog:publiclogItfc,publicstd::enable_shared_from_this<customLog>{// 使用 shared_from_this() 获取 shared_ptr};Q3: 如何传递配置参数?
A: 通过构造函数传递。createLog使用完美转发:
autolog=createLog<tinyLogType::CUSTOM>(4,arg1,arg2,arg3);Q4: 线程池如何使用?
A:pt_runner__是线程池指针,可用于异步任务:
voidvlog(...){if(pt_runner__){pt_runner__->pushTask([this,entry](){// 异步处理});}}
编程学习
技术分享
实战经验