microLog 后端开发指南

📅 2026/7/5 6:57:06 👁️ 阅读次数 📝 编程学习
microLog 后端开发指南

microLog 后端开发指南

概述

microLog 采用抽象工厂模式门面模式进行架构设计,能够方便地扩展新的日志后端。本文档将逐步说明如何开发自定义后端实现,并提供完整的示例与最佳实践。

文中示例代码仅为展示原理,并没有进行编译测试

架构设计

持有 mutex、threadPool,格式化在调用线程完成

用户代码
auto log = createLog<tinyLogType::CUSTOM>(args...);
log->info("hello world");

LOG<Backend> 门面层(模板类)

后端层(自定义实现)
- 继承 logItfc
- 实现 vlog / get_fd__ / is_valid__

logFactoryHelper 抽象工厂
注册所有后端类型,通过索引创建实例

核心接口

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.hpptinyLogType枚举中添加新类型:

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.hpplogFactoryHelper中添加新后端:

#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_packagetarget_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-j4

2. 添加测试

创建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

已有后端参考

后端文件路径特点
LOCALlogDetail/local.hpp环形文件,嵌入式友好
UDPlogDetail/udp.hpp无连接,高性能
TCPlogDetail/tcp.hpp可靠传输
PIPElogDetail/pipe.hpp进程间通信
UNIXSOCKlogDetail/unixSock.hpp本地套接字
TERMINALlogDetail/terminal.hpp控制台输出,彩色支持
SQLITE3logDetail/sqlite3_log.hpp嵌入式数据库
MYSQLlogDetail/mysql_log.hpp双连接 + 事务批量提交
POSTGRESQLlogDetail/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](){// 异步处理});}}