具体完整实践代码,跳转
log4cpp具体实现
(1)高吞吐量 100万条/s 批量写入很重要
能否做到高吞吐量,缓存是最关键
(2)如果待机,有哪些日志没有写进去是什么原因?
假如10W条消息刷一次盘?肯定不行,如果宕机,就很多消息没有记录到库
(3)同步日志,异步日志
(4)glog支持批量写入,不支持配置文件
(5)log4cplus本身带了异步写入
(6)log4cpp也支持异步,可以自己设置
(7) log4不支持专门打印同一级别的错误
好的日志log要求:
(1)缓存的数据长度,比如10k,写一次盘
(2)有个时间计数,已经1秒没有刷新,则需要刷新了
(3)同步日志,异步日志
同步日志:单个线程,支持批量写入才能高效,如果不支持批量写入会很一般
异步日志:2个线程,1个负责格式化,另一个线程负责批量写入
三、项目解析
1,ostreamAppender是输出到本地
2,category是一个单例工厂,不同名字不同日志
3,setAdditivity(false)表示不用父类的东西,设置为true,表示Category的Appender包含了父类Category的Appender补充:子类继承父类Category----有2个appender,1个写入文件,1个终端输出
4,setPriority()设置category的优先级,低于此优先级的日志不被记录,例如可以设置打印>=error级别
5,不支持只打印某一个级别
6,log4cpp::Category::shutdown(); 静态函数,退出时做牵引工作,将日志主件卸载,避免内存泄漏
7.virtual void addAppender(*Appender appender);//appender的生命周期被函数内部(category)接管,删除也是内存处理
virtual void addAppender(Appender& appender);//不把Appender的所有权交给category管理,需要自己删除
8.callAppenders()中加锁互斥(异步)调用appender写入信息,真正写入信息的时doAppend,log4app效率不高的原因是没有做缓存,都是_append()里面直接write,直接写入的
9.总体关系
Category英中翻译过来是“种类”,但我的理解它应该带有“归类”的意思。它的机制更像是“部⻔组织机构”,具有树型结构。它将⽇志按“区域”划分。
Log4cpp有且只⼀个根Category,可以有多个⼦Category组成树型结构。Category具有Priority、additivity属性与若⼲⽅法。下⾯列出所有的属性与常⽤⽅法。
1.属性:Name
Category的名称。不可重复。
相关⽅法:
virtual const std::string& getName () const throw();
2.属性:Priority
⽇志的优先级(详情请⻅Priority章节):
①对于根Category,必须指定Priority。(priority < Priority::NOTSET)
②对于⾮根Category,可以不指定Priority,此时优先级继承⾃⽗Category。
③对于⾮根Category,也可以指定Priority,此时优先级覆盖⽗Category的优先级。
相关⽅法: (重点)
//设置当前Category的优先级
virtual void setPriority (Priority::Value priority);
//获取当前对象的优先级
virtual Priority::Value getPriority () const throw();
// 设置root Category的优先级
static void setRootPriority(Priority::Value priority);
// 获取root Category的优先级
static Priority::Value getRootPriority() throw();
// 获取当前category继承表中的优先级,如果当前的优先值没有设置的话,则找他的⽗亲,
// 如果⽗亲没有找到的话,他会继续向上找,因为root Category的优先值默认为Priority::INFO,
// 所以肯定是能够找到的
virtual Priority::Value getChainedPriority () const throw();
// 返回当前拥有priority优先级
virtual bool isPriorityEnabled (Priority::Value priority) const throw();
3.属性:additivity
每个Category都有⼀个additivity属性,该属性默认值为true。
①如果值为true,则该Category的Appender包含了⽗Category的Appender。
②如果值为false,则该Category的Appender取代了⽗Category的Appender。
相关⽅法
virtual void setAdditivity (bool additivity);
virtual bool getAdditivity () const throw();
4.属性:parent
上级Category。根Category的parent为空。
相关⽅法
virtual Category* getParent () throw();
virtual const Category* getParent () const throw();
5.⽅法:getRoot
静态⽅法。取得根Category。
static Category& getRoot();
6.⽅法:getInstance
静态⽅法。取得指定名称(参数name)的Category,如果不存在,则⾃动创建⼀个以name命名,parent为rootCategory,Priority为INFO的Category(说⽩了就是⼀个⼯⼚⽅法,不要和单例模式混淆了)。
static Category& getInstance(const std::string& name);
7.⽅法:exists(一般没啥用)
静态⽅法。判断是否存在指定名称(参数name)的Category。如果存在,则返回相应的Category(指针),否则返回空指针。
static Category* exists(const std::string& name);
8.⽅法:shutdown
静态⽅法。从所有的Category中移除所有的Appender。
static void shutdown();
9.⽅法:shutdownForced
静态⽅法。从所有的Category中移除所有的Appender,并且删除所有的Appender。(shutdown⽅法
只是不使⽤Appender,并没有彻底从内存中销毁Appender)。
static void shutdownForced();
10.⽅法:Appender相关
// 添加⼀个Appender到Category中。
// 该⽅法将把Appender的所有权交给Category管理
virtual void addAppender (Appender* appender);
// 添加⼀个Appender到Category中。
// 但是该⽅法并不把Appender的所有权交给Category管理
virtual void addAppender (Appender& appender);
// 获取指定名字的Appender(指针)。如果不存在则返回空指针
virtual Appender* getAppender (const std::string& name) const;
// 获取所有的Appender,以指针的⽅式存储在set中
virtual AppenderSet getAllAppenders () const;
// 删除所有的Appender
virtual void removeAllAppenders ();
// 删除指定的Appender
virtual void removeAppender (Appender* appender);
// 判断指定Appender是否被当前Category拥有所有权。
// 如果是的话,在删除该Appender时,将同时销毁它。
virtual bool ownsAppender (Appender* appender) const throw();
11.⽅法:⽇志相关
Category的⽇志输出⽅式有两种,⼀种是简单的传递std::string类型的字符串,⼀种是采⽤类似c api中的printf,可以格式化⽣成字符串。
1) 以指定的优先级⽣成⽇志
virtual void log (Priority::Value priority,
const std::string& message) throw();
2)以指定的优先级与格式化⽣成⽇志,类似c api中的printf⽅法
virtual void log (Priority::Value priority, const char* stringFormat,
…) throw();
3)格式化⽣成⽇志的⼀种变体,直接传递va_list参数
virtual void logva (Priority::Value priority, const char* stringFormat,
va_list va) throw();
4) 除了以上三种通⽤⽇志三种⽅法外,Log4cpp还指供了多种预定义Priority的⽅法,如:
void debug(const char* stringFormat, …) throw();
void info(const char* stringFormat, …) throw();
。。。
5)例子
log4cpp::Category& rootCategory = log4cpp::Category::getRoot();
rootCategory.error(“This is error message\n”);
rootCategory.log(log4cpp::Priority::DEBUG, “This is %d message\n”, 8);
12.⽅法: CategoryStream
除了C⻛格的⽅法外,Log4cpp还指供了c++⻛格的流式输出⽅法。模彷std::iostream,Log4cpp也指供了CategoryStream辅助类。使每个CategoryStream对象封装⼀种优先级的⽇志输出
1)例子
// 获取(⽣成)指定优先级的CategoryStream
virtual CategoryStream getStream(Priority::Value priority);
// 对运算符“<<”的重载。也是获取(⽣成)指定优先级的CategoryStream
virtual CategoryStream operator<<(Priority::Value priority);
// 快捷⽅法。还有infoStram()等预定义优先级的快捷⽅法。
inline CategoryStream debugStream();
2)实际适用性
在使⽤时,CategoryStream,应该只作为临时对象,因为它总是在对象销毁时才会真正将⽇志输出(flush)。
当然你可以强制调⽤CategoryStream的flush⽅法,但这样会使代码很难看(⽇志代码跟实际业务⽆关,代码⾏
应该尽可能的少,并且可以随时屏蔽⽇志功能)。
例⼦:
log4cpp::Category& root = log4cpp::Category::getRoot();
root << log4cpp::Priority::ERROR << “This is error message”;
root.debugStream() << “This is " << 8 << " message”;
(3.1)总体Appender类
(3.2)具体使用的Appender类
1.FileAppender
①作用:输出到⽂件系统。
②构造函数:
FileAppender(const std::string& name, const std::string& fileName,
bool append = true, mode_t mode = 00644);
③缺点:容易把内存写爆,工作中不建议用④相关参数:
2.DailyRollingFileAppender
①⼀种特例化的FileApppender,⽂件系统以天为单位进⾏管理,当⽣成⽇志时的⽇期发⽣变化时,将会⽣成新的⽇志⽂件。
②构造函数:
DailyRollingFileAppender(const std::string& name,
const std::string& fileName,
unsigned int maxDaysToKeep = maxDaysToKeepDefault,
bool append = true,
mode_t mode = 00644);
③适用性:可在工作中使用,但没有限制每个文件的大小
④相关参数
3.RollingFileAppender
①作用:作⽤:⼀种特例化的FileAppender,⽂件系统是⽂件⼤⼩为单位,当⽇志⽂件的⼤⼩达到预设值时,将会⽣成新的⽇志⽂件
②构造函数:
RollingFileAppender(const std::string& name,
const std::string& fileName,
size_t maxFileSize = 1010241024,
unsigned int maxBackupIndex = 1,
bool append = true,
mode_t mode = 00644);
③相关参数:
④特殊注意:写入文件,如果文件大小超过指定的,先把当前文件改名,然后新建文件,名字往后推,如先.log->.1(改为.1),然后.1->.2,(每次都会判断文件的大小,所以回滚速度会慢一点),满了就会把最先的名字.5删除逐一改名
1.概述:Layout控制输出⽇志的显示样式。Log4cpp内置了4种Layout。
2.PassThroughLayout
直通布局。顾名思义,这个就是没有布局的“布局”,你让它写什么它就写什么,它不会为你添加任何东⻄,连换⾏符都懒得为你加。
3.SimpleLayout
简单布局。它只会为你添加“优先级”的输出。相当于PatternLayout格式化为:“%p: %m%n”。
4.PatternLayout
格式化布局。它的使⽤⽅式类似C语⾔中的printf,使⽤格式化它符串来描述输出格式。⽬前⽀持的转
义定义如下:
%% - 转义字符’%’
%c - Category
%d - ⽇期;⽇期可以进⼀步设置格式,⽤花括号包围,例如%d{%H:%M:%S,%l}。
⽇期的格式符号与ANSI C函数strftime中的⼀致。但增加了⼀个格式符号%l,表示毫秒,占三
个⼗进制位。
%m - 消息
%n - 换⾏符;会根据平台的不同⽽不同,但对⽤户透明。
%p - 优先级
%r - ⾃从layout被创建后的毫秒数
%R - 从1970年1⽉1⽇开始到⽬前为⽌的秒数
%u - 进程开始到⽬前为⽌的时钟周期数
%x - NDC
%t - 线程id
1)⼿动使⽤log4cpp的基本步骤如下:
2)代码
// FileName: test_log4cpp1.cpp
#include "log4cpp/Category.hh"
#include "log4cpp/FileAppender.hh"
#include "log4cpp/OstreamAppender.hh"
#include "log4cpp/BasicLayout.hh"
#include "log4cpp/SimpleLayout.hh"
// 编译 g++ -o 2-test_log4cpp 2-test_log4cpp.cpp -llog4cpp
int main(int argc, char *argv[])
{
// 1实例化一个layout 对象
log4cpp::Layout *layout = new log4cpp::SimpleLayout(); // 有不同的layout
// 2. 初始化一个appender 对象
log4cpp::Appender *appender = new log4cpp::FileAppender("FileAppender",
"./2-test_log4cpp.log");
log4cpp::Appender *osappender = new log4cpp::OstreamAppender("OstreamAppender",
&std::cout);
// 3. 把layout对象附着在appender对象上
appender->setLayout(layout);
// appender->addLayout 没有addLayout,一个layout格式样式对应一个appender
// 4. 实例化一个category对象
log4cpp::Category &warn_log =
log4cpp::Category::getInstance("darren"); // 是一个单例工厂
// 5. 设置additivity为false,替换已有的appender
warn_log.setAdditivity(false);
// 5. 把appender对象附到category上
warn_log.setAppender(appender);
warn_log.addAppender(osappender);
// 6. 设置category的优先级,低于此优先级的日志不被记录
warn_log.setPriority(log4cpp::Priority::INFO);
// 记录一些日志
warn_log.info("Program info which cannot be wirten, darren = %d", 100);
warn_log.warn("Program info which cannot be wirten, darren = %d", 100);
warn_log.debug("This debug message will fail to write");
warn_log.alert("Alert info");
// 其他记录日志方式
warn_log.log(log4cpp::Priority::WARN, "This will be a logged warning, darren = %d", 100);
warn_log.warnStream() << "This will be a logged warning, darren = " << 100;
log4cpp::Priority::PriorityLevel priority;
bool this_is_critical = true;
if (this_is_critical)
priority = log4cpp::Priority::CRIT;
else
priority = log4cpp::Priority::DEBUG;
warn_log.log(priority, "Importance depends on context");
warn_log.critStream() << "This will show up << as "
<< 1 << " critical message";
// clean up and flush all appenders
log4cpp::Category::shutdown();
return 0;
}
编译:g++ -o test_log4cpp1 test_log4cpp1.cpp -llog4cpp -lpthread
3)配置⽂件驱动⽅式使⽤步骤
基本使⽤步骤是:
1 # ⽂件名: test_log4cpp2.conf
2 # a simple test config
3 #定义了3个category sub1, sub2, sub1.sub2
4 log4j.rootCategory=DEBUG, rootAppender
5 log4j.category.sub1=,A1
6 log4j.category.sub2=INFO
7 log4j.category.sub1.sub2=ERROR, A2
8 # 设置sub1.sub2 的additivity属性
9 log4j.additivity.sub1.sub2=false
10 #定义rootAppender类型和layout属性
11 log4j.appender.rootAppender=org.apache.log4j.ConsoleAppender
12 log4j.appender.rootAppender.layout=org.apache.log4j.BasicLayout
13 #定义A1的属性
14 log4j.appender.A1=org.apache.log4j.FileAppender
15 log4j.appender.A1.fileName=A1.log
16 log4j.appender.A1.layout=org.apache.log4j.SimpleLayout
17 #定义A2的属性
18 log4j.appender.A2=org.apache.log4j.ConsoleAppender
19 log4j.appender.A2.layout=org.apache.log4j.PatternLayout
20 #log4j.appender.A2.layout.ConversionPattern=The message ‘%m’ at time
%d%n
21 log4j.appender.A2.layout.ConversionPattern=%d %m %n
4)源码
// FileName: test_log4cpp2.cpp
// Test log4cpp by config file.
#include "log4cpp/Category.hh"
#include "log4cpp/PropertyConfigurator.hh"
#include "log4cpp/NDC.hh"
// 编译:g++ -o 3-test_log4cpp 3-test_log4cpp.cpp -llog4cpp -lpthread
void test(log4cpp::Category& category)
{
log4cpp::NDC::push(__FUNCTION__); // 记录NDC信息
category.info("我最强");
log4cpp::NDC::pop();
}
int main(int argc, char* argv[])
{
// 1 读取解析配置文件
// 读取出错, 完全可以忽略,可以定义一个缺省策略或者使用系统缺省策略
// BasicLayout输出所有优先级日志到ConsoleAppender
try {
log4cpp::PropertyConfigurator::configure("./3-test_log4cpp.conf");
} catch(log4cpp::ConfigureFailure& f) {
std::cout << "Configure Problem " << f.what() << std::endl;
return -1;
}
// 2 实例化category对象
// 这些对象即使配置文件没有定义也可以使用,不过其属性继承其父category
// 通常使用引用可能不太方便,可以使用指针,以后做指针使用
// log4cpp::Category* root = &log4cpp::Category::getRoot();
log4cpp::Category& root = log4cpp::Category::getRoot();
log4cpp::NDC::push(__FUNCTION__); // 记录NDC信息
// log4cpp::NDC::push("ndc2");
log4cpp::Category& sub1 =
log4cpp::Category::getInstance(std::string("sub1"));
log4cpp::Category& sub3 =
log4cpp::Category::getInstance(std::string("sub1.sub2"));
// 3 正常使用这些category对象进行日志处理。
// sub1 has appender A1 and rootappender.
sub1.info("sub1 This is some info");
test(root);
root.info("root This is some info");
// sub1.alert("sub1 A warning");
// // sub3 only have A2 appender.
// sub3.debug("sub3 This debug message will fail to write");
// sub3.alert("sub3 All hands abandon ship");
// sub3.critStream() << "sub3 This will show up << as " << 1 << " critical message";
// sub3 << log4cpp::Priority::ERROR
// << "sub3 And this will be an error";
// sub3.log(log4cpp::Priority::WARN, "sub3 This will be a logged warning");
// clean up and flush all appenders
log4cpp::Category::shutdown();
return 0;
}
编译:g++ -o test_log4cpp2 test_log4cpp2.cpp -llog4cpp -lpthread