# 日志系统
# 日志系统的重要性
- 错误跟踪:当系统出现问题时,日志是诊断问题的重要工具。通过分析日志,可以追踪错误发生的原因和上下文。
- 安全审计:日志可以记录安全相关的事件,如登录尝试、权限变更等,这对于检测和响应安全威胁至关重要。
- 性能监控:日志可以帮助监控系统性能,通过分析日志中的性能指标,可以发现潜在的性能瓶颈。
- 系统维护:日志提供了系统运行的历史记录,有助于理解系统的运行模式和预测可能的故障。
# 日志系统的分类
- 业务日志:记录业务操作的详细情况,如交易处理、订单状态变更等,有助于业务分析和问题追踪。
- 系统日志:记录系统运行时的关键信息,如启动、关闭、错误、警告等,有助于系统维护和故障排查。
# 日志系统的设计原则
- 性能影响最小化:日志系统应该设计为对系统性能的影响尽可能小,避免成为性能瓶颈。
- 可配置性:日志系统应该允许配置不同的日志级别和输出格式,以适应不同的需求。
- 可靠性:日志系统本身应该是高度可靠的,确保在任何情况下都能记录日志。
- 易于查询和分析:日志应该易于查询和分析,可以考虑使用专门的日志分析工具。
- 安全和隐私:确保日志中不包含敏感信息,遵守相关的数据保护法规。
- 灵活的存储和管理:日志应该可以存储在不同的介质上,并提供灵活的管理策略,如日志轮转、归档和删除。
# 日志系统的设计
# 日志系统的设计原则
在设计一个日志系统时,应该考虑以下几个关键点:
- 灵活性:日志系统应该能够灵活地适应不同的需求,包括不同的日志级别、格式和输出目标。
- 可扩展性:随着系统的发展,日志系统应该能够容易地扩展新的功能和特性。
- 性能:日志系统的性能影响应该最小化,以避免对主应用程序的性能造成负面影响。
- 可靠性:日志系统应该是可靠的,即使在高负载或异常情况下也能保证日志数据不丢失。
- 易于配置和使用:应该能够容易地配置和使用日志系统,而不需要深入了解其内部实现。
# 日志系统的四个主要部分
- 记录器:负责捕获和记录日志的原始信息,如时间戳、日志级别、发生位置和线程信息。
- 过滤器:根据预设的条件(如日志级别、日志内容等)来决定是否记录某条日志。
- 格式化器:将日志信息格式化为指定的格式,如日期、时间、日志级别和消息内容的组合。
- 输出器:负责将格式化后的日志信息输出到不同的目的地,如控制台、文件、数据库或远程日志服务器。
下面以一条日志的生命周期为例说明日志库是怎么工作的。
一条日志的生命周期:
- 产生:
info(“log information.”)
; - 经过记录器,记录器去获取日志发生的时间、位置、线程信息等等信息;
- 经过过滤器,决定是否记录;
- 经过格式化器处理成设定格式后传递给输出器。例如输出
2018-3-22 10:00:00 [info] log information.
这样格式的日志到文件中。日志的输出格式由格式化器实现,输出目的地则由输出器决定; - 这条日志信息生命结束。
# log4cpp 安装
# Linux
下载压缩包,下载地址:https://sourceforge.net/projects/log4cpp/files/Windows
安装:
tar xzvf log4cpp-1.1.4rc3.tar.gz | ||
cd log4cpp | ||
./configure # 进行自动化构建,自动生成 makefile | ||
make | ||
sudo make install # 安装,把头文件和库文件拷贝到系统路径下 |
安装后,默认头文件路径为: /usr/local/include/log4cpp
,默认 lib 库路径为: /usr/local/lib
打开 log4cpp 官网 Log for C++ Project (sourceforge.net)
拷贝 Simple example 的内容,编译运行
编译指令: g++ log4cppTest.cc -llog4cpp -lpthread
运行时可能会报错:
解决方法:
cd /etc | ||
sudo vim ld.so.conf |
将默认的 lib 库路径写入,再重新加载
sudo ldconfig |
让动态链接库为系统所共享
ld.so.cache 执行了 $ sudo ldconfig
之后,会更新该缓存文件,会将所有动态库信息写入到该文件。当可执行程序需要加载相应动态库时,会从这里查找。
完成这些操作后,再使用上面的编译指令去编译示例代码。
# Windows
参考:C++ Log4cpp 跨平台日志库使用记录 (Window 与 Linux)
# log4cpp 的核心组件
#include "log4cpp/Appender.hh" | |
#include "log4cpp/BasicLayout.hh" | |
#include "log4cpp/Category.hh" | |
#include "log4cpp/FileAppender.hh" | |
#include "log4cpp/Layout.hh" | |
#include "log4cpp/OstreamAppender.hh" | |
#include "log4cpp/Priority.hh" | |
using namespace log4cpp; | |
int main(int argc, char** argv) { | |
// 基类指针指向派生类对象 | |
/* Appender * appender1 = new OstreamAppender("console", &std::cout); */ | |
// 创建输出器对象 | |
// OstreamAppender 的构造函数 | |
// 第一个参数代表目的地名称(随便写) | |
// 第二个参数是一个 ostream*,真正决定了日志的目的地 | |
OstreamAppender* appender1 = new OstreamAppender("console", &std::cout); | |
// 输出器绑定布局 | |
appender1->setLayout(new BasicLayout()); | |
// 创建输出器对象 | |
// FileAppender 的构造函数 | |
// 第一个参数代表目的地名称(随便写) | |
// 第二个参数代表日志所保存到的文件名 | |
FileAppender* appender2 = new FileAppender("default", "program.log"); | |
// 输出器绑定布局 | |
appender2->setLayout(new BasicLayout()); | |
// 创建根节点级别的 Category 对象 | |
// 在程序中以引用的形式使用 | |
Category& root = Category::getRoot(); | |
// 设置系统的优先级 | |
root.setPriority(Priority::WARN); | |
// 添加目的地 (绑定输出器) | |
root.addAppender(appender1); | |
// 创建叶子节点级别的 Category 对象(root 下一级的) | |
// 会继承父节点的优先级和目的地 | |
// | |
// 前一个 sub1 是引用的名称,供程序员在代码中使用 | |
// 后一个 sub1 (getInstance 的参数) 代表日志来源 | |
Category& sub1 = Category::getInstance("sub1"); | |
// 也可以重新设置门槛(系统优先级) | |
sub1.setPriority(Priority::ERROR); | |
sub1.addAppender(appender2); | |
/* sub1.setAdditivity (false);// 不继承父节点的目的地 */ | |
//root 的子节点 | |
/* Category& sub2 = Category::getInstance("sub2"); */ | |
//sub1 的子节点 | |
/* Category& sub11 = Category::getInstance("sub1.sub11"); */ | |
// 写入日志 | |
// 函数的名称直接对应该日志信息的优先级 | |
// use of functions for logging messages | |
root.error("root error"); // 1 | |
root.warn("root warn"); // 1 | |
root.info("root info"); // 0 | |
sub1.crit("sub1 crit"); // 1 | |
sub1.error("sub1 error"); // 1 | |
sub1.warn("sub1 warn"); // 0 | |
// printf-style for logging variables | |
root.warn("%d + %d == %s ?", 1, 1, "two"); // 1 | |
// use of streams for logging messages | |
root << Priority::ERROR << "Streamed root error"; // 1 | |
root << Priority::INFO << "Streamed root info"; // 0 | |
sub1 << Priority::ERROR << "Streamed sub1 error"; // 1 | |
sub1 << Priority::WARN << "Streamed sub1 warn"; // 0 | |
// or this way: | |
root.errorStream() << "Another streamed error"; // 1 | |
return 0; | |
} |
# 日志目的地(Appender)
# OstreamAppender
类
OstreamAppender
类的构造函数在 log4cpp 库中用于初始化日志记录器,以便将日志消息输出到一个指定的输出流(ostream)。在 C++ 中,ostream 是一个用于输出数据到不同目的地(如标准输出、文件等)的类。
在 log4cpp 的 OstreamAppender
类中,构造函数通常具有以下形式:
OstreamAppender::OstreamAppender(const std::string& name, std::ostream* target) |
- name: 这是一个
std::string
类型的参数,用于指定日志记录器的名称。这个名称用于在配置文件中标识这个特定的日志记录器。 - target: 这是一个指向
std::ostream
的指针,指向你想要日志消息输出到的目标。这可以是std::cout
(标准输出),std::cerr
(标准错误),或者指向一个文件的std::ofstream
对象。
# FileAppender
类
FileAppender
类在 log4cpp 库中用于将日志信息输出到文件。这个类提供了两个构造函数,允许你以不同的方式初始化日志文件的输出。
构造函数 1
FileAppender::FileAppender(const std::string& name, const std::string& fileName, bool append, mode_t mode = 00644) |
参数:
name
: 日志记录器的名称。fileName
: 日志信息将要写入的文件名。append
: 布尔值,如果为true
,则日志信息将追加到文件末尾;如果为 false,则文件会被截断(即原有内容会被删除)。mode
: 文件的权限模式,通常使用八进制表示法。默认值为 00644,表示文件所有者具有读写权限,而组用户和其他用户只有读权限。
构造函数 2
FileAppender::FileAppender(const std::string& name, std::FILE* fd) |
参数:
name
: 日志记录器的名称。fd
: 已经打开的文件描述符,日志信息将写入到这个文件描述符指向的文件。
功能说明
- 构造函数 1: 这个构造函数是最常见的用法,它允许你指定日志文件的名称、是否追加以及文件的权限模式。这种方式下,
FileAppender
会负责打开和关闭文件。 - 构造函数 2: 这个构造函数允许你传递一个已经打开的文件描述符。这种方式下,
FileAppender
不会关闭文件,因为文件的打开和关闭由调用者控制。这种方式通常用于文件已经被外部程序打开,并且希望FileAppender
直接使用这个文件描述符进行日志写入的场景。
# RollingFileAppender
类
RollingFileAppender
的构造函数用于初始化日志文件滚动记录器。构造函数允许你指定日志文件的名称、最大文件大小、备份文件数量、是否追加到现有文件以及文件的权限。
以下是 RollingFileAppender
构造函数的一般形式:
RollingFileAppender::RollingFileAppender( | |
const std::string& filename, | |
const std::string& layout, | |
size_t maxFileSize = 10 * 1024 * 1024, | |
unsigned int maxBackupIndex = 1, | |
bool append = true, | |
mode_t mode = 00644 | |
); |
参数解释:
filename
: 日志文件的名称。layout
: 用于格式化日志消息的布局对象。maxFileSize
: 日志文件达到这个大小时,将触发滚动。单位通常是字节。maxBackupIndex
: 指定可以保留的旧日志文件的数量。例如,如果设置为 3,则会保留 3 个旧文件。append
: 布尔值,表示是否追加到现有日志文件。如果为 true,则追加;如果为 false,则覆盖。mode
: 用于设置文件权限的模式。这是一个位掩码,用于定义文件的权限。
构造函数的作用:
- 初始化日志文件:根据提供的文件名创建或打开日志文件。
- 设置布局:使用提供的布局对象来格式化日志消息。
- 设置滚动策略:根据
maxFileSize
和maxBackupIndex
参数设置滚动策略。 - 设置文件模式:根据
mode
参数设置文件的权限。
当日志文件达到预设的大小限制时,旧的日志文件会被重命名,新的日志文件被创建来继续记录日志的过程。这种机制有助于控制日志文件的大小,避免无限制地增长,同时保留历史日志记录以供分析。
以下是滚动机制的详细步骤:
- 检查文件大小:每当有日志消息写入时,
RollingFileAppender
会检查当前日志文件的大小。 - 触发滚动:如果当前日志文件的大小超过了
setMaxFileSize
方法设置的最大文件大小,就会触发滚动。 - 重命名旧文件:触发滚动后,
RollingFileAppender
会将当前的日志文件重命名。重命名的规则通常是在文件名后面添加一个后缀,后缀通常是数字,表示这是第几个备份文件。例如,如果maxBackupIndex
设置为 3,那么可能会有 example.log.1、example.log.2、example.log.3 这样的文件。 - 删除最旧的备份:如果有超过
maxBackupIndex
设置数量的备份文件,最旧的备份文件会被删除,以释放空间。 - 创建新文件:滚动完成后,会创建一个新的日志文件,文件名与原始日志文件相同,用于继续记录新的日志消息。
- 继续记录:新的日志文件创建后,日志记录操作会继续在这个新文件中进行。
# 日志布局(Layout)
示例代码中使用的是 BasicLayout
,也就是默认的日志布局,这样一条日志最开始的信息就是日志产生时距离 1970.1.1 的秒数,不方便观察。
实际使用时可以用 PatrrenLayout
对象来定制化格式,类似于 printf
的格式化输出
在 log4cpp 中, PatternLayout
类的构造函数用于初始化日志的模式布局。这种布局方式允许你定义日志消息的格式,这通常包括日期、日志级别、消息内容等。
无参构造函数
PatternLayout::PatternLayout() |
这个构造函数不接受任何参数。它将使用默认的日志模式。默认模式通常是 %m%n
,其中 %m
表示日志消息, %n
是行分隔符。
带参数的构造函数
PatternLayout::PatternLayout(const std::string& pattern) |
这个构造函数接受一个字符串参数,允许你在创建 PatternLayout 对象时直接指定日志的格式。这里的 pattern 参数是你希望日志消息遵循的模式。
参数解释:
%c
- 日志事件的类别(logger name)- 例如:
%c{1}
表示仅包含 logger name 的第一个字符。
- 例如:
%d
- 日志事件的日期- 例如:
%d{%a %b %d %H:%M:%S %Y}
表示按星期、月份、日期、小时、分钟、秒、年份的格式输出日期。
- 例如:
%m
- 日志事件的消息- 表示日志消息的文本。
%n
- 平台特定的行分隔符- 在 Unix/Linux 上是换行符
\n
,在 Windows 上是回车换行\r\n
。
- 在 Unix/Linux 上是换行符
%p
- 日志事件的优先级(日志级别)- 例如:
DEBUG
、INFO
、WARN
、ERROR
、FATAL
。
- 例如:
%r
- 自日志事件创建到当前时间的毫秒数。%t
- 当前线程的名字。%x
- 与当前线程关联的 NDC(Nested Diagnostic Context)。%%
- 字面上的百分号(%)
转换模式还可以包含自定义的日期格式,使用 % d 后跟花括号 {} 内的格式字符串。例如:
%d{HH: mm: ss}
- 24 小时制的小时、分钟和秒。%d{dd MMM yyyy}
- 日、月份缩写和年份。
转换模式还可以包含可选的修饰符,如:
%-5c
- 左对齐,至少占 5 个字符的宽度。%5c
- 右对齐,至少占 5 个字符的宽度。%.2c
- 截断,取 logger name 的前两个字符。
转换模式还可以包含可选的格式化信息,如:
%d{%a %b %d %H:%M:%S %Y}
- 完整的日期和时间。
使用这些转换符号,你可以创建非常灵活的日志格式,以满足你的特定需求。例如,一个常见的日志格式可能是:
%d{%Y-%m-%d %H:%M:%S} [%c] %-5p %m%n
这个格式将输出如下的日志行:
2024-09-25 14:35:59 [com.example.MyLogger] INFO This is a log message.
其中:
%d{%Y-%m-%d %H:%M:%S}
输出日期和时间。[%c]
输出日志类别(logger name)。%-5p
输出日志级别,至少占 5 个字符的宽度。%m
输出日志消息。%n
输出行分隔符。
注意:当日志系统有多个日志目的地时,每一个目的地 Appender
都需要设置一个布局 Layout
(一对一关系)
# 日志记录器(Category)
在 log4cpp 库中,创建 Category
对象的过程可以通过以下步骤进行:
-
创建根类别:首先,使用
getRoot
方法创建一个根类别对象。这个对象是日志系统的顶级类别,可以为其设置日志优先级和附加器(Appender)。 -
设置根类别属性:接下来,可以为根类别对象设置优先级和附加器,这些设置将影响所有未明确设置优先级和附加器的子类别。
-
创建子类别:然后,使用
getInstance
方法创建子类别对象。这些子类别对象将继承根类别的优先级和附加器设置,但也可以在需要时重写这些设置。 -
修改子类别属性:子类别对象可以进一步修改其优先级和附加器,以满足特定模块的日志需求。
-
隐式创建根对象:如果未显式创建根对象,直接使用
getInstance
方法创建子类别时,log4cpp 会隐式地创建一个根对象。
子类别继承父类别信息:子类别可以继承父类别的优先级和附加器信息。
示例代码:
// 创建根类别对象并设置优先级和附加器 | |
log4cpp::Category &root = log4cpp::Category::getRoot(); | |
root.setPriority(log4cpp::Priority::WARN); | |
root.addAppender(appender1); | |
// 创建子类别对象并添加附加器 | |
log4cpp::Category &sub1 = log4cpp::Category::getInstance("sub1"); // 传入的字符串 sub1 就会是日志中记录下的日志来源 | |
sub1.addAppender(appender2); |
简化创建子类别:
// 一行代码创建子类别对象 | |
log4cpp::Category& sub1 = log4cpp::Category::getRoot().getInstance("salesDepart"); | |
sub1.setPriority(log4cpp::Priority::WARN); | |
sub1.addAppender(appender1); |
注意事项:
sub1
是一个引用,用于操作Category
对象,包括设置优先级、添加附加器和记录日志。getInstance
的参数salesDepart
定义了日志信息中的类别名称,也就是日志的来源,对应布局中的%c
占位符。- 通常,为了清晰地识别日志来源,子类别的名称应与
getInstance
的参数保持一致。
# 日志优先级(Priority)
在 log4cpp 日志系统中,我们关注两类优先级:一是日志记录器(Category 对象)的优先级,二是具体日志条目的优先级。设置 Category 对象时,需要指定其优先级。当记录一条日志时,如使用 logger.debug("this is a debug message")
,这条日志的优先级即为 DEBUG 级别。简而言之:
日志系统有一个阈值优先级 A,日志条目有一个指定优先级 B。
只有当 B 大于或等于 A 时,该日志条目才会被记录;如果 B 低于 A,则该日志条目将被忽略。
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; | |
//... | |
}; // 数值越小,表示优先级越高;数值越大,表示优先级越低 |
# 定制日志系统
模仿示例代码的形式去设计定制化的日志系统:
#include <iostream> | |
#include "log4cpp/OstreamAppender.hh" | |
#include "log4cpp/FileAppender.hh" | |
#include "log4cpp/RollingFileAppender.hh" | |
#include "log4cpp/Category.hh" | |
#include "log4cpp/Priority.hh" | |
#include "log4cpp/PatternLayout.hh" | |
using std::cout; | |
using std::endl; | |
using namespace log4cpp; | |
int main() { | |
// 1. 创建布局对象 | |
auto *ptn1 = new PatternLayout(); | |
ptn1->setConversionPattern("%d %c [%p] %m%n"); | |
auto *ptn2 = new PatternLayout(); | |
ptn2->setConversionPattern("%d %c [%p] %m%n"); | |
auto *ptn3 = new PatternLayout(); | |
ptn3->setConversionPattern("%d %c [%p] %m%n"); | |
// 2. 创建输出器对象 | |
auto *pos = | |
new OstreamAppender("console", &cout); | |
pos->setLayout(ptn1); | |
auto *pfile = | |
new FileAppender("file", "log.txt"); | |
pfile->setLayout(ptn2); | |
auto pRoll = new RollingFileAppender("rolling", "res.txt", 5 * 1024, 9); | |
// 输出器与布局绑定 | |
pRoll->setLayout(ptn3); | |
// 3. 创建记录器对象 | |
// 引用名 salesDepart 是在代码中使用的,表示 Category 对象 | |
// 参数中 salesDepart 是获取冂志来源时返回的记录器的名字 | |
// 一般让两者相同,方便理解 | |
Category & sub1 = Category::getInstance("sub1"); | |
// 4. 设置优先级 | |
sub1.setPriority(Priority::DEBUG); | |
// 5. 设置输出器 | |
sub1.addAppender(pos); | |
sub1.addAppender(pfile); | |
sub1.addAppender(pRoll); | |
// 6. 写入日志 | |
int count = 30; | |
while (count-- > 0) { | |
sub1.emerg("this is an emerg msg"); | |
sub1.fatal("this is a fatal msg"); | |
sub1.alert("this is an alert msg"); | |
sub1.crit("this is a crit msg"); | |
sub1.error("this is an error msg"); | |
sub1.warn("this is a warn msg"); | |
sub1.notice("this is a notice msg"); | |
sub1.info("this is an info msg"); | |
sub1.debug("this is a debug msg"); | |
} | |
// 7. 回收资源 | |
Category::shutdown(); | |
return 0; | |
} |
在设计日志系统时多次使用了 new
语句,这些核心组件的构造函数具体细节我们也并不清楚,但可以知道的是这个过程必然会申请资源,所以规范的写法在日志系统退出时要调用 shutdown
回收资源。
# log4cpp 的单例实现
参考:C++ day07 log4cpp 的实际使用习题 (有答案)
MyLogger.h
#ifndef __MY_LOGGER_H__ | |
#define __MY_LOGGER_H__ | |
#include "log4cpp/Category.hh" | |
using namespace log4cpp; | |
class Mylogger { | |
public: | |
// 单例类创建函数 | |
static Mylogger *getInstance(); | |
// 释放单例类 | |
static void destroy(); | |
// 警告 | |
void warn(const char *msg); | |
// 错误 | |
void error(const char *msg); | |
// 检测 | |
void debug(const char *msg); | |
// 通知 | |
void info(const char *msg); | |
private: | |
// 构造函数 | |
Mylogger(); | |
// 析构函数 | |
~Mylogger(); | |
private: | |
static Mylogger *_pInstance; | |
//category,定义错误等级 | |
Category &_mycat; | |
}; | |
// 宏定义,用于绑定输出错误日志的函数及错误行号 | |
#define prefix(msg) (string(__FILE__) + string(" ")+ string(__FUNCTION__) \ | |
+ string(" ")+ string(std::to_string(__LINE__)) + string(" ") \ | |
+ msg).c_str() | |
// 定义具体错误类型 | |
#define LogError(msg) Mylogger::getInstance()->error(prefix(msg)) | |
#define LogInfo(msg) Mylogger::getInstance()->info(prefix(msg)) | |
#define LogWarn(msg) Mylogger::getInstance()->warn(prefix(msg)) | |
#define LogDebug(msg) Mylogger::getInstance()->debug(prefix(msg)) | |
#endif // __MY_LOGGER_H__ |
MyLogger.cpp
#include "MyLogger.h" | |
#include "log4cpp/PatternLayout.hh" | |
#include "log4cpp/OstreamAppender.hh" | |
#include "log4cpp/FileAppender.hh" | |
#include "log4cpp/Priority.hh" | |
#include <iostream> | |
using std::cout; | |
using std::endl; | |
Mylogger *Mylogger::_pInstance = nullptr; | |
Mylogger *Mylogger::getInstance() { | |
if (nullptr == _pInstance) { | |
_pInstance = new Mylogger(); | |
} | |
return _pInstance; | |
} | |
void Mylogger::destroy() { | |
if (_pInstance) { | |
delete _pInstance; | |
_pInstance = nullptr; | |
} | |
} | |
void Mylogger::warn(const char *msg) { | |
_mycat.warn(msg); | |
} | |
void Mylogger::error(const char *msg) { | |
_mycat.error(msg); | |
} | |
void Mylogger::debug(const char *msg) { | |
_mycat.debug(msg); | |
} | |
void Mylogger::info(const char *msg) { | |
_mycat.info(msg); | |
} | |
Mylogger::Mylogger() | |
: _mycat(log4cpp::Category::getInstance("Mycat")) { | |
// 日志的格式 | |
auto *ppl1 = new PatternLayout(); | |
ppl1->setConversionPattern("%d %c [%p] %m %n"); | |
auto *ppl2 = new PatternLayout(); | |
ppl2->setConversionPattern("%d %c [%p] %m %n"); | |
// 日志的目的地 | |
auto *poa = new OstreamAppender("OstreamAppender123", &cout); // 输出到终端 | |
poa->setLayout(ppl1); | |
// 输出到日志文件 | |
auto *pfa = new FileAppender("FileAppender123", "wd.log"); | |
pfa->setLayout(ppl2); | |
_mycat.addAppender(poa); | |
_mycat.addAppender(pfa); | |
// DEBUG 及以上危险等级的日志都要输出 | |
_mycat.setPriority(Priority::DEBUG); | |
} | |
// 析构函数就是执行 shutdown () 文件,整体退出 | |
Mylogger::~Mylogger() { | |
Category::shutdown(); | |
} |
TestLog.cpp
#include "MyLogger.h" | |
#include <iostream> | |
#include <string> | |
using std::cout; | |
using std::endl; | |
using std::string; | |
// 测试用,在实际中不用 | |
string func(const char *msg) { | |
string s1 = string(__FILE__) + string(" ")+ string(__FUNCTION__) | |
+ string(" ")+ string(std::to_string(__LINE__)) + string(" ") | |
+ msg; | |
return s1; | |
} | |
// 实际使用日志返利 | |
void test1() { | |
Mylogger *ps1 = Mylogger::getInstance(); | |
ps1->error("error1"); | |
Mylogger::getInstance()->error("error2"); | |
Mylogger::getInstance()->error(func("error3").c_str()); | |
Mylogger::getInstance()->error(prefix("helloworld")); | |
Mylogger::getInstance()->warn(prefix("helloworld")); | |
Mylogger::getInstance()->info(prefix("helloworld")); | |
Mylogger::getInstance()->debug(prefix("helloworld")); | |
LogError("error3"); | |
const char *pstr = "hello"; | |
int number = 1; | |
int value = 2; | |
// 可变参数输出 | |
LogError("hello, %d, %s %d\n", number, pstr, value); | |
} | |
//test2 在实际中不用 | |
void test2() { | |
cout << __FILE__ << " " << __FUNCTION__ << " " << __LINE__ << endl; | |
} | |
int main(int argc, char **argv) { | |
test1(); | |
return 0; | |
} |
# log4cpp 配置文件读取
如果想要更灵活地使用 log4cpp,可以使用读取配置文件的方式
// log4cpp.properties | |
log4cpp.rootCategory=DEBUG, rootAppender | |
log4cpp.category.sub1=DEBUG, A1, A2 | |
log4cpp.category.sub1.sub2=DEBUG, A3 | |
log4cpp.appender.rootAppender=ConsoleAppender | |
log4cpp.appender.rootAppender.layout=PatternLayout | |
log4cpp.appender.rootAppender.layout.ConversionPattern=%d [%p] %m%n | |
log4cpp.appender.A1=FileAppender | |
log4cpp.appender.A1.fileName=A1.log | |
log4cpp.appender.A1.layout=BasicLayout | |
log4cpp.appender.A2=FileAppender | |
log4cpp.appender.A2.threshold=WARN | |
log4cpp.appender.A2.fileName=A2.log | |
log4cpp.appender.A2.layout=PatternLayout | |
log4cpp.appender.A2.layout.ConversionPattern=%d [%p] %m%n | |
log4cpp.appender.A3=RollingFileAppender | |
log4cpp.appender.A3.fileName=A3.log | |
log4cpp.appender.A3.maxFileSize=200 | |
log4cpp.appender.A3.maxBackupIndex=1 | |
log4cpp.appender.A3.layout=PatternLayout | |
log4cpp.appender.A3.layout.ConversionPattern=%d [%p] %m%n |
#include <log4cpp/Category.hh> | |
#include <log4cpp/PropertyConfigurator.hh> | |
int main(int argc, char* argv[]) | |
{ | |
std::string initFileName = "log4cpp.properties"; | |
log4cpp::PropertyConfigurator::configure(initFileName); | |
log4cpp::Category& root = log4cpp::Category::getRoot(); | |
log4cpp::Category& sub1 = | |
log4cpp::Category::getInstance(std::string("sub1")); | |
log4cpp::Category& sub2 = | |
log4cpp::Category::getInstance(std::string("sub1.sub2")); | |
root.warn("Storm is coming"); | |
sub1.debug("Received storm warning"); | |
sub1.info("Closing all hatches"); | |
sub2.debug("Hiding solar panels"); | |
sub2.error("Solar panels are blocked"); | |
sub2.debug("Applying protective shield"); | |
sub2.warn("Unfolding protective shield"); | |
sub2.info("Solar panels are shielded"); | |
sub1.info("All hatches closed"); | |
root.info("Ready for storm."); | |
log4cpp::Category::shutdown(); | |
return 0; | |
} |