cpp-tbox项目链接 https://gitee.com/cpp-master/cpp-tbox
更多精彩内容欢迎关注微信公众号:码农练功房
往期精彩内容:
Linux应用框架cpp-tbox之弱定义
简介
cpp-tbox的日志系统有如下特点:
1) 有三种日志输出渠道:stdout + filelog + syslog
- stdout,将日志通过
std::cout
输出到终端; - syslog,将日志通过
syslog()
输出到系统日志; - filelog,将日志写入到指定目录下,以格式:
前缀.年月日_时分秒.进程号.log
的文件中。文件大小超过1M则另创建新的日志文件。由于写文件效率低,该输出渠道采用前后端模式。
三种渠道可以在启动参数中选定一个或同时多种,也可在运行时通过终端更改。
2) 根据日志等级渲染不同颜色,一目了然,内容详尽
日志内容包含了:等级、时间(精确到微秒)、线程号、模块名、函数名、正文、文件名、行号。方便快速定位问题。
3) 灵活的日志输出过滤器,且能运行时修改
可在程序运行时针对不同的模块单独设置日志等级,如下:
整体结构图
下图是cpp-tbox中日志系统的代码模型,这里只标识出了主干接口,同时为了排版方便,去除了有些接口的参数,但是不影响我们理解整个结构。
输出通道
cpp-tbox支持三种日志输出渠道,可以在启动参数中选定一个或同时多种输出渠道,也可在运行时通过终端更改。这个是怎么实现的呢?
在cpp-tbox\modules\base\log_imp.cpp
中你可以找到答案:
// cpp-tbox\modules\base\log_imp.cpp
struct OutputChannel {
uint32_t id;
LogPrintfFuncType func;
void *ptr;
};
std::vector<OutputChannel> _output_channels;
这里在源文件的匿名空间中定义了OutputChannel这个数据结构,其中ptr为指向各个具体Sink类的this指针,func为函数指针,指向具体Sink类的日志处理接口。
需要使能某种输出方式时,把对应类添加到这个输出通道。要关闭某种输出方式时,把对应类从通道中删除即可。
通道删除、添加由以下接口实现,其中id主要在关闭输出方式时使用:
// cpp-tbox\modules\base\log_imp.cpp
uint32_t LogAddPrintfFunc(LogPrintfFuncType func, void *ptr)
bool LogRemovePrintfFunc(uint32_t id)
通道添加、删除接口被Sink::enable和Sink::disable接口所调用。
日志数据包
在日志前端每条日志都会被打包成如下数据结构,送给输出通道。
// cpp-tbox\modules\base\log_imp.cpp
struct LogContent {
long thread_id; //!< 线程ID
struct {
uint32_t sec; //!< 秒
uint32_t usec; //!< 微秒
} timestamp; //!< 时间戳
const char *module_id; //!< 模块名
const char *func_name; //!< 函数名
const char *file_name; //!< 文件名
int line; //!< 行号
int level; //!< 日志等级
uint32_t text_len; //!< 内容大小
const char *text_ptr; //!< 内容地址
};
在输出通道中,最终会调用Sink::onLogFrontEnd这个日志前端接口(纯虚接口),而这个接口由各个派生类实现,这里用到了模板方法(Template Method)。
// cpp-tbox\modules\log\sink.cpp
// Sink::HandleLog被注册到OutputChannel.func
void Sink::HandleLog(const LogContent *content, void *ptr)
{
Sink *pthis = static_cast<Sink*>(ptr);
pthis->handleLog