当做大型项目的时候,出了bug可能需要借助于日志检查,小项目一般是打断点。
服务器是一直在运行的,不能停止,可以借助于日志检查错误。
日志分为两种:业务级别的日志(供用户分析业务过程),系统级别的日志(用来供程序员分析)
因为日志文件占据的存储空间很大,会打印很多的信息,难以检查错误信息,所以需要进行合理的设计。
本章节主要学习日志记录系统log4cpp。
日志系统包含四类信息:分别是:记录器、过滤器、格式化器、输出器四部分。
记录器:记录信息的来源,时间,优先级,位置。
过滤器:并不是把所有的 信息都保存下来。有时候把不重要的信息过滤掉。
格式化器:将日志信息设置布局,这样更方便查看。
输出器:设置日志目的地,例如存储到文件。
ostreamAppender是一个Appender的派生类
基类指针指向派生类对象
基类引用访问派生类对象(std::getline返回一个istream,使之与iftream绑定)
ostreamAppender的第一个参数只是给程序员一个提醒内容无关紧要,第二个参数是ostream* 这个地方就是使用了&cout,当然也可以绑定文件流也就是派生类。
basiclayout 将距离1970年。。有多少秒,所以说我们在自己用的时候不用这个。
root节点是通过单例模式进行创建在栈上的对象
当日志优先级比系统优先级要低就直接过滤掉。
然后就是创建叶子级别的catgr对象,继承父节点的优先级和目的地
如果没有创建根对象,直接使用getInstance创建叶对象,会先隐式地创建一个Root对象。
通过.来显示分级
后面的sub1代表日志的来源,也就是输出在终端的字符串内容
如果修改节点的写入目的地,那么会将信息写入到自定义的文件中。
如果是root对象调用的就只是保存到root的目的地,如果(子节点继承根节点目的地时)是sub子节点对象那么根节点和子节点的目的地都保存。
也可以设置不同目的地不同日志文件系统级别。
有四种形式可以写入终端
直接调用函数
//sub1.error()
流
函数+流的方式
#include "log4cpp/OstreamAppender.hh"
#include "log4cpp/Layout.hh"
#include "log4cpp/BasicLayout.hh"
#include "log4cpp/Priority.hh"
//Category 日志记录器
//Appender 表示输出器目的地
//Layout 表示显示格式格式化器
//Priority 表示优先级过滤器
int main(int argc, char** argv) {
log4cpp::Appender *appender1 = new log4cpp::OstreamAppender("console", &std::cout);
//OstreamAppender 是Appender的派生类
//其中的第一个参数只是给用户看的,内容是什么无关紧要,“console”表示控制台也就是屏幕终端显示
//第二个参数很重要表示将打印信息输出到的位置
appender1->setLayout(new log4cpp::BasicLayout());
//本行命令表示的是输出格式BasicLayout表示的是将时间输出为从19701月1号开始的的秒数
//一般我们不会使用这个,因为秒数不能很好的辨识时间
log4cpp::Appender *appender2 = new log4cpp::FileAppender("default", "program.log");
//同上只是表示将输出写到日志文件program.log中
//注意这样是以追加的方式写入
appender2->setLayout(new log4cpp::BasicLayout());
log4cpp::Category& root = log4cpp::Category::getRoot();
//创建category对象的时候首先先创建一个单例的root对象,
//并在下面的语句中设置根对象的系统级别以及输出目的地
root.setPriority(log4cpp::Priority::WARN);
root.addAppender(appender1);
//设置叶节点,前面的&引用sub1就是表示返回一个在栈上的这个节点,
//后面的这个sub1就是用于打印的时候显示
//并且设置目的输出地,当然也可以额外设置输出的系统等级,如果都不设置的话都是跟随根节点
//当然可以不创建root节点直接创建叶节点,系统会自动隐式创建一个根节点
log4cpp::Category& sub1 = log4cpp::Category::getInstance(std::string("sub1"));
//og4cpp::Category& sub1 = log4cpp::Category::getInstance(std::string("sub1.sub11"));
//这样就可以在sub1下面实现再连接一个sub11叶节点
sub1.addAppender(appender2);
// use of functions for logging messages
root.error("root error");//ok等级大于warn会被输出到根节点设置的屏幕
root.info("root info");//error等级低
sub1.error("sub1 error");//ok等级足够,会被同时打印到根节点设置的屏幕和叶节点设置的文件
sub1.warn("sub1 warn");//ok等级足够会被输出到文件中去和屏幕中
// printf-style for logging variables
root.warn("%d + %d == %s ?", 1, 1, "two");
// use of streams for logging messages
root << log4cpp::Priority::ERROR << "Streamed root error";
root << log4cpp::Priority::INFO << "Streamed root info";
sub1 << log4cpp::Priority::ERROR << "Streamed sub1 error";
sub1 << log4cpp::Priority::WARN << "Streamed sub1 warn";
// or this way:
root.errorStream() << "Another streamed error";
return 0;
}
【注意】这个地方可以将前面的一大长串的变量设置为auto类型。这个地方更加深入了解auto,auto是可以推导出变量类型,也可以推导出指针类型。但是auto不能推导出引用类型。
log4cpp官网 Log for C++ Project
日志目的地
主要关注三个目的地类分别是
• OstreamAppender C++通用输出流(如 cout)
• FileAppender 写到本地文件中
• RollingFileAppender 写到回卷文件中
- OstreamAppender的构造函数传入两个参数:目的地名(随便写)、输出流指针
- FileAppender的构造函数传入两个参数:目的地名、保存日志的文件名(后面两个参数使用默认值即可,分别表示以结尾附加的方式的保存日志,当前用户读写-其他用户只读)
RollingFileAppender使用场景就是比如说是因为系统不断产生文件,如果对于存储空间不加限制会大量的占用空间,所以说划分一块区域保存最新的一部分日志文件
【注意】RollingFileAppender构造函数的参数如上图,其中要注意的是回卷文件个数,如果这一位传入的参数是9,那么实际上会有10个文件保存日志。
回卷的机制是:先生成一个wd.log文件,该文件存满后接着写入日志,那么wd.log文件改名为wd.log.1,然后再创建一个wd.log文件,将日志内容写入其中,wd.log文件存满后接着写入日志,wd.log.1文件改名为wd.log.2,wd.log改名为wd.log.1,再创建一个wd.log文件,将最新的日志内容写入。以此类推,直到wd.log和wd.log.1、wd.log.2、... wd.log.9全都存满后再写入日志,wd.log.9(其中实际上保存着最早的日志内容)会被舍弃,编号在前的回卷文件一一进行改名,再创建新的wd.log文件保存最新的日志信息。
日志布局
本来的默认布局时BasicLayout,这种方式是表示距离1970.1.1的秒数不方便观察。
因此调用的时候可以使用PatrrenLayout对象来定制化格式
设置日志布局的方式
PatternLayout * ptn1 = new PatternLayout();
ptn1->setConversionPattern("%d %c [%p] %m%n");
该字符串的含义:
%d %c [%p] %m%n
时间 模块名 优先级 消息本身 换行符
【注意】日志系统有多个日志目的地的时候,每一个目的地Appender都需要设置一个布局Layout(一对一的关系)
日志记录器
创建category对象时,可以先使用getRoot创建root模块对象,对于root模块对象设置优先级和目的地。
再用getInstance创建叶模块,叶模块会继承root模块的优先级和目的地,当然也可以重新设置
如果没有创建根对象直接使用getInstance创建叶对象,会隐式创建一个跟对象
//官网示例分开创建的方式
log4cpp::Category& root = log4cpp::Category::getRoot();
root.setPriority(log4cpp::Priority::WARN);
root.addAppender(appender1);
log4cpp::Category& sub1 = log4cpp::Category::getInstance(std::string("sub1")); //传入的字符串sub1就会是日志中记录下的日志来源
sub1.addAppender(appender2);
//直接创建叶对象的方式
log4cpp::Category& sub1 = log4cpp::Category::getRoot().getInstance("salesDepart"); //记录的日志来源会是salesDepart
sub1.setPriority(log4cpp::Priority::WARN);
sub1.addAppender(appender1);
前面的sub1本质是绑定到category的引用,在代码中可以使用sub1设置优先级添加目的地以及记录日志。
getIInstance中的参数salesDepart表示的是日志信息中记录的category的名称,也就是打印信息中的日志来源对应%c
在使用的时候这两者的名称取同一个名称,可以清楚该日志是来自于salesDepart这个模块
日志优先级
主要注意两个优先级分别是日志记录器的优先级(即系统优先级)和日志的优先级(某一条日志信息的优先级)。
只有当日志信息的优先级高于等于系统的优先级的时候,这一条日志信息才会被打印出来,否则这条日志会被过滤掉。
class LOG4CPP_EXPORT Priority {
public:
typedef enum {
EMERG = 0,
FATAL = 0,
ALERT = 100,
CRIT = 200,
ERROR = 300,
WARN = 400,
NOTICE = 500,
INFO = 600,
DEBUG = 700,
NOTSET = 800 //这个不代表可以使用的优先级
} PriorityLevel;
//......
}; //数值越小,优先级越高;数值越大,优先级越低
日志布局:
PatternLayout * ptn1 = new PatternLayout();
ptn1->setConversionPattern("%d %c [%p] %m%n");
%d %c [%p] %m%n
时间 模块名 优先级 消息本身 换行符
%d设置时间地格式
头文件:输出到终端
使用自定义格式地头文件
记录器,过滤器
【了解】如果是使用根节点的话那么在布局中%c位置就不会有打印信息。
【重点】前面的sub1是用来进行使用接下来的绑定操作,在后面的sub1用来显示打印信息,同时还有显示父子叶节点关系“(后面的)sub1.sub11”。注意这个地方是用的后面的sub1!!子节点的信息不仅会打印到自己的输出位置中,还需要输出到父节点的输出目的地中。
表示通过getRoot创建根节点,通过getInstance创建叶节点。
也可以不加上getroot这样的话就就隐式的创建根节点。
这样做的话可能需要再额外定义一个root节点才能再继续进行操作根节点,
3.结束以后基本就将所有的信息绑定在一起
【了解】1.log4cpp在设计的时候有一个bug就是enum设计两个类型都是一个0表示都会视为下面那个fatal,反正这两种都是需要立即进行执行
2. 了解官网给的版本就是没有shutdown函数,但是可能也不会有内存泄漏,也有可能泄漏进行了优化。
日志目的地
【注意】shutdown回收资源,对于delete的封装,无论在日志系统中前面new多少次的话。
【重点】需要将目的地和格式绑定,然后将记录器和目的地绑定,记得两次绑定
布局对象和目的地对象需要一一对应,这样的话不会出现段错误
创建目的地的对象是文件对象的时候,可以有两种方式:
一种是通过oftream ofs。绑定在原本的&cout位置。当然也可以使用fileAppender,直接相应的位置输入文件名字符串
【注意】日志文件在fileAppender方式下,是以追加的方式进行添加
日志文件在OstreamAppender方式下,是以截断添加的方式添加
还有一种文件目的地,就是回卷文件
就是将空间分为几个文件,然后不断循环保存最新的内容
例如:下例中就是最新的文件一直都是wd.log
先生成一个wd.log文件,该文件存满后接着写入日志,那么wd.log文件改名为wd.log.1,然后再创建一个wd.log文件,将日志内容写入其中,wd.log文件存满后接着写入日志,wd.log.1文件改名为wd.log.2,wd.log改名为wd.log.1,再创建一个wd.log文件,将最新的日志内容写入。以此类推,直到wd.log和wd.log.1、wd.log.2、... wd.log.9全都存满后再写入日志,wd.log.9(其中实际上保存着最早的日志内容)会被舍弃,编号在前的回卷文件一一进行改名,再创建新的wd.log文件保存最新的日志信息。
【注意】回卷文件的个数如果是是5,实际的文件的个数就是6
log4cpp主要是使用category这个引用对象,所以说在private的位置保存一个category的引用。
【重点】获取文件名称和调用函数以及所在的列数的时候,使用的时候c的宏,这个处理是是在预处理阶段来实现的,所以说这个拼接字符串的操作是不能使用普通的函数的这样的话只会显示这个定义拼接字符串函数的文件和行号。可能会想到inline进行替换但是是在编译阶段实现使用的_FILE_是在,