log4cplus 是一个灵活的日志库,不仅仅可以跨平台,功能强大,受到了 Java 的 log4j 库的启发,并为 C++ 设计。它提供了丰富的日志级别、日志格式和输出目标的配置选项,使得开发者能够根据应用程序的需要灵活地记录信息。
灵活性:log4cplus 提供了多种日志级别和输出选项,支持异步和同步日志记录。
易用性:它的 API 简单直观,易于集成到现有项目中。
可配置性:可以通过配置文件或编程方式配置日志行为。
log4cplus 的工作原理基于几个核心组件:记录器(Logger)、布局(Layout)和附加器(Appender)。记录器负责生成日志消息,布局负责格式化日志消息,附加器负责将格式化后的消息输出到不同的目标。
记录器是日志系统的入口点,开发者通过记录器记录消息。每个记录器都有一个名字和日志级别,只有当消息的级别高于或等于记录器的级别时,消息才会被记录。
布局负责将日志消息转换为字符串,并按照特定的格式输出。log4cplus 提供了多种内置的布局,如简单布局、模式布局等。
附加器定义了日志消息的输出目标。常见的附加器有控制台附加器、文件附加器等。开发者可以根据需要配置一个或多个附加器。
log4cplus 的性能受到多种因素的影响,包括日志级别、输出目标和日志消息的复杂性。一般来说,异步日志记录的性能要优于同步日志记录。
日志级别越高,记录的消息越少,性能越好。在生产环境中,建议将日志级别设置得较高,以提高性能。
将日志消息输出到控制台通常比输出到文件要慢。如果对性能有较高要求,建议将日志消息输出到内存或网络。
log4cplus 支持异步日志记录,这可以显著提高性能,特别是在高负载环境下。
log4cplus 提供了丰富的配置选项,允许开发者灵活地控制日志输出。
开发者可以通过配置文件来设置日志级别、布局和附加器,实现灵活的输出控制。
除了配置文件,log4cplus 也允许开发者通过编程方式来配置日志系统。
https://sourceforge.net/projects/log4cpp/files/latest/downloa
enum LogLevel {
TRACE,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
NUM_LOG_LEVELS,
};
在使⽤⽇志库的时候要注意不同⽇志库的级别有差异,⽐如有些⽇志库 DEBUG和INFO的级别是反过来 的。
一般日志输出时会携带一些关注的信息,比如
20210410 14:18:15.299684Z 30836 INFO NO.1506710 Root Error Message! - log_test.cpp:17
格式包含:年月日 时分秒 微妙 时区 日志级别 日志内容 文件名 行号。
比如Log4cpp支持的转义定义:
◼ %% - 转义字符’%’
◼ %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
log4cpp支持的日志布局有:
(1)BasicLayout::format。 基本布局。它会为你添加“时间”、“优先级”、“种类”、“NDC”。相当于PatternLayout格式化为:“%R %p %c %x: %m%n”。
(2) PassThroughLayout::format。 直通布局。顾名思义,这个就是没有布局的“布局”,你让它写什么它就写什么,它不会为你添加任何东西,连换行符都懒得为你加。但是,它支持自定义的布局,我们可以继承他实现自定义的日志格式。
(3) PatternLayout::format log4cpp。 格式化布局,支持用户配置日志格式。它的使用方式类似C语言中的printf,使用格式化它符串来描述输出格式;目前支持的转义定义。
(4) SimpleLayout::format 。 简单布局。比BasicLayout还简单的日志格式输出,它只会为你添加“优先级”的输出。相当于PatternLayout格式化为:“%p: %m%n”。
日志有不同的输出方式,以log4cpp为例: (1)日志输出到控制台。ConsoleAppender。 (2)日志输出到本地文件。FileAppender,值得注意的是,log4cpp使用write()来输出到文件,这种方式的性能不会太高i,因为要频繁的切换用户态和内核态。这是一个可以优化的地方。 (3)日志通过网络传输到远程服务器。RemoteSyslogAppender。
日志库一般都具备如下功能: (1)本地日志支持最大文件限制。 (2)当本地日志达到最大文件限制的时候新建一个文件。 (3)每天至少一个文件。
比如log4cpp里的RollingFileAppender()。
RollingFileAppender(
const std::string& name,
const std::string& filename,
size_t maxFileSize=10*1024*1024//日志文件支持最大的大小
unsigned int maxBackupIndex=1,//最大生成文件数量
bool append=true,// 是否以追加的方式写入
mode_t mode = 00644
);
成熟的日志文件要支持配置文件,通过修改配置文件来改变日志输出方式而不需要修改代码。
比如log4cpp日志配置:
#指定rootCategory的log优先级是ERROR,其Appenders有两个,分别是console,TESTAppender
log4cpp.rootCategory=ERROR, console,TESTAppender
#-------定义console属性-------
#consoleAppender类型:控制台输出
#下面这三条语句表示控制台输出的log输出的布局按照指定的格式;输出格式是:[%p] %d{%H:%M:%S.%l} (%c): %m%n
log4cpp.appender.console=ConsoleAppender
log4cpp.appender.console.layout=PatternLayout
log4cpp.appender.console.layout.ConversionPattern=[%p] %d{%H:%M:%S.%l} (%c): %m%n
#-------定义TESTAppender的属性-------
#RollingFileAppender类型:输出到回卷文件,即文件到达某个大小的时候产生一个新的文件
#下面的语句表示文件输出到指定的log文件,输出的布局按照指定的格式,输出的格式是:[%d{%Y-%m-%d %H:%M:%S.%l} - %p] (%c): %m%n
log4cpp.appender.TESTAppender=RollingFileAppender
#当日志文件到达maxFileSize大小时,将会自动滚动
log4cpp.appender.TESTAppender.maxFileSize=400000
#maxBackupIndex指定可以产生的滚动文件的最大数
log4cpp.appender.TESTAppender.maxBackupIndex=3
#fileName指定信息输出到logs/TESTAppender.txt文件
log4cpp.appender.TESTAppender.fileName=logs/TESTAppender.txt
#PatternLayout 表示可以灵活指定布局模式
log4cpp.appender.TESTAppender.layout=PatternLayout
#append=true 信息追加到上面指定的日志文件中,false表示将信息覆盖指定文件内容
log4cpp.appender.TESTAppender.append=true
log4cpp.appender.TESTAppender.layout.ConversionPattern=[%d{%Y-%m-%d %H:%M:%S.%l} - %p] (%c): %m%n
ConversionPattern的参数含义:
%d 输出日志时间点的日期或时间,可以在其后指定格式,如上%d{%Y-%m-%d %H:%M:%S.%l},输出类似:2017-02-14 09:25:00.953
%p 优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%c 输出日志信息所属的类目,通常就是所在类的全名
%m 输出log的具体信息
%n 回车换行
读取配置文件示例代码:
#include "log4cpp/Category.hh"
#include "log4cpp/PropertyConfigurator.hh"
int main(int argc,char *argv[])
{
//读取解析配置文件
try
{
log4cpp::PropertyConfigurator::configure("./log4cpp.conf");
}
catch (log4cpp::ConfigureFailure &f)
{
std::cout << "Configure Problem " << f.what() << std::endl;
return -1;
}
// 实例化category对象
log4cpp::Category &root = log4cpp::Category::getRoot();
// 正常使用这些category对象进行日志处理。
root.debug("root debug");
root.info("root info");
root.notice("root notice");
root.warn("root warn");
root.error("root error");
root.crit("root crit");
root.alert("root alert");
root.fatal("root fatal");
root.emerg("root emerg");
// ...
// clean up and flush all appenders
log4cpp::Category::shutdown();
return 0;
}
主要分为几个步骤: (1) 读取解析配置文件。读取出错, 完全可以忽略,可以定义一个缺省策略或者使用系统缺省策略。 (2)2 实例化category对象。这些对象即使配置文件没有定义也可以使用,不过其属性继承其父category;通常使用引用可能不太方便,可以使用指针,以后做指针使用。 (3)使用这些category对象进行日志处理。 (4)清理并刷新所有的appenders。
数字越大,数据越久。 当文件内容和文件数据都达到设定的阈值时,会执行如下操作(假如设置了最大文件数是5):
(1)删除数字最大的文件。删除log5.txt。
(2)文件重命名。
(3)新建log文件。
(1)实时写入磁盘 单笔 write。 多行(100行)日记累积再写入 -> 累积了99行,下一行一直不来。 a.单独起一个定时器(比如1秒)去刷新。 b.同一个日志管理线程去刷新数据。 注:实际glog他是累积一定的行数或者过了一定的时间间隔就让刷新。
(2) 回滚日志每次都取读取日志文件大小 肯定不能每次读取文件大小。
异步日志,从批量写入和超时方向的考量。
日志落盘线程,比如超过100条写入磁盘、超时一秒钟写入磁盘。
glog日志线程逻辑相对简单。 (1)积累一定数量日志再写入。 (2)超时写入。比如,第一条日志1秒的时候写入;第二条日志3秒的时候写入。跟上一次刷新超过一定的时间,也会把所有的缓存日志刷新磁盘中。
这种方式支持批量写入,但不是最好的解决方案。
通过剖析log4cpp日志库的日志框架,可以清晰一个成熟的、完善的日志框架应该支持:
(1)日志级别。 (2)日志格式化。 (3)日志输出方式。 (4)日志回滚。 (5)日志配置文件。
注意:
(1)日志的树状模块输出。 (2)支持多种输出方式,每种输出方式都可以有不同的格式化。 (3)log4cpp代码结构组织。
(4)不要直接把log4cpp用在服务器中,因为log4cpp的性能稍差