今天在写一个日志模块,遇到一些问题,学到了一些东西,记一下。
一开始想到,cpp 这么成熟的社区,日志这么基本的模块,应该现成的有很多吧,随便找一个来用就可以了,上网搜了一圈,发现真是一团混乱啊,看到这里,http://blog.csdn.net/junchaox/…
待选为 glog、log4cplus、log4cpp、log4cxx
目前准备使用glog,使用方便,性能也不错,待进一步试验,如果有不能满足的功能就转用 log4cplus,功能很全面,不过稍复杂些。
其它两个都是三年前就没更新,没好感,暂不准备使用。
1.log4cplus
最新版本:1.1.0 2012-03-11下载地址:http://sourceforge.net/projects/log4cplus/files/log4cplus-stable/1.1.0
功能全面,使用稍复杂。
代码示例:
#include <log4cplus/layout.h> #include <log4cplus/configurator.h> #include <iomanip> SharedAppenderPtr pFileAppender(new FileAppender("testlog.log")); Logger pTestLogger = Logger::getInstance("LoggerName"); pTestLogger.addAppender(pFileAppender); sprintf(a,"%d",i); LOG4CPLUS_WARN(pTestLogger, "This is a <Warn> log message..." << a );2.log4cxx
最新版本: 0.10.0 2008-04-03下载地址:http://logging.apache.org/log4cxx/download.html
编译很麻烦 新的版本0.10.0需要使用Apache的产品Ant来编译,而Ant又需要JDK。。。怕麻烦,没有下载测试。
3. glog
最新版本: 0.3.2 2012-1-12下载地址:http://code.google.com/p/google-glog/downloads/list
使用方便,性能也不错。
Google Glog 是一个C++语言的应用级日志记录框架,提供了 C++ 风格的流操作和各种助手宏。
Google glog是一个基于程序级记录日志信息的c++库,编程使用方式与c++的stream操作类似
代码示例:
#include <glog/logging.h> google::InitGoogleLogging(argv[0]); google::SetLogDestination(google::INFO,"./myInfo_"); LOG(INFO) << "This is a <Warn> log message..." << i;4. Log4cpp
最新版1.0.x 2007-09-03下载地址: http://sourceforge.net/project…
感觉 跟 log4cplus类似,结构稍简单些,不过代码也不少写。
#include <log4cpp/Category.hh> #include <log4cpp/PropertyConfigurator.hh> #include <log4cpp/NDC.hh> #include <log4cpp/FileAppender.hh> #include <log4cpp/BasicLayout.hh> log4cpp::Layout* layout = new log4cpp::BasicLayout(); log4cpp::Appender* appender = new log4cpp::FileAppender("FileAppender", "./test_log4cpp1.log"); appender->setLayout(layout); log4cpp::Category& warn_log = log4cpp::Category::getInstance("mywarn"); warn_log.setAdditivity(false); warn_log.setAppender(appender); warn_log.setPriority(log4cpp::Priority::WARN); warn_log.critStream() << "This is a <Warn> log message..." << i;
看到上面的那些东西,真是令人望而生畏,我这才理解为什么之前看到的几个开源项目都是自己实现了一个日志类,好吧,cpp 程序员就是喜欢重复造轮子,术语叫什么来着,not invented here
好吧,既然求开源而不得,那只能自己动手丰衣足食了,总的来说,我们希望这个日志模块满足以下几个特点:
- 易用,最好是只要 include 一个头文件,然后就可以用了,退一步,我们可以接受在使用之前调用一次 init 函数,不需要另外编译依赖库,更不要安装。
- 轻量,对业务的能耗尽量减少到最小。
- 实现简洁。用尽量少的代码实现。
- 线程安全。
- 输出层级可控。
- 异步落地磁盘,而且按照文件大小或者时间自动滚动。
而最终的效果,使用上,我们希望是这样的用法。
init_log(); LOG_DEBUG("test log %d", 123); LOG_ERR("test err %s", "456");
这个地方,之所以用 c 的转义,而不用 cpp 的流式,一个是考虑到效率,一个是考虑到现有的代码兼容。
首先上来遇到的第一个问题,我们需要打印行号,文件名,函数名等等,这些怎么得到呢,想到用宏,__LINE__,__FILE__,等等,但是,宏展开之后,需要调用我们自己的函数吧,这些可变参数怎么从我们的函数传到 printf 呢?
在这个地方纠结了很久,一开始去找各种 va_list 的传递方法,发现这个几乎是不可能的,于是后面又找了 vsnprintf 这种的函数,才发现有这么一类可以直接接收 va_list 的函数存在。
然后就是日志类的初始化问题,一开始,想到用类的静态函数来做,但是又发现,这样的话,也是有问题的,于是后面用单例来做。
然后就是异步的问题,这个是想到先把日志存到内存,然后用线程异步写入磁盘文件。但是遇到问题是,内存要怎么布局呢,如果我们把内存连续的用起来,那么,多个线程同时写的时候,怎么解决字符串交错的问题呢,如果用锁,那么性能必然大大下降。
另外的一个问题是,我们申请一片定长内存,然后把它当作循环队列来用,但是,在快到结尾的时候,例如,假如还剩下 50 个字符的空间,但是有一条日志过来了,而这个日志要占用 100 个字符,那怎么处理剩下的 50 个呢,按照循环队列的意义,应该转到头部继续写的,但是,到时候怎么打印出来呢。如果不截断的话,有什么办法解决呢。而且更麻烦的问题是,在一个字符串被格式化完成之前,我们是不知道这个字符串会有多长的。
对于这个问题,有一个办法是,我们留下一部分内存空间,当作保留区,例如,我们申请 15M,但是只有 14M 是拿来当循环队列的,那么,当指针超过 14 的时候,下一条就回来开始的地方了,这样的话,就要限定,刚好在 14 M 边缘的那条,单行不能超过 1MB,这个限制,应该还是可以接受的。
但是依然没有解决多线程的并发写入问题。
这个问题,或许用一个二维的字符型数组来实现,然后用下标的原子操作来避免多线程混叠,但是,这样的话,我们就需要按照最长的可能单行来分配,那么,就必然造成大量的空间浪费,特别是在最长的单行比大多数的单行都长很多倍的情况下。
待续。
iphone开发下面有个开源日志还可以打印图片的….客户端输出还不是控制台 碉堡了
堂堂 c++ 一大帮人在用,居然连个日志标准都没有。。