文章目录
0、概念
- 为什么要记录日志而不是使用print打印:https://cloud.tencent.com/developer/article/1613436
- 为什么选择Spdlog:Spdlog是一个快速、异步、线程安全的C++日志库,他可以方便地记录应用程序的运行状态,并提供多种输出格式。
- 官网:https://github.com/gabime/spdlog
Spdlog的优点:
- 只包含头文件
- 速度很快
- 无需依赖第三方库
- 支持跨平台
- 支持多线程—线程安全
- 可对日志文件进行循环输出
- 可每日生成日志文件
- 可支持控制台日志输出
- 可选的异步输出
- 可定义日志格式
1、安装使用方式
Spdlog安装方式:
- 包管理器安装方式(我当前使用的方式)
- 由于我使用的系统是ubuntu,所以我就是用了包管理器的方式进行安装
sudo apt-get install libspdlog-dev
- 源码安装方式
- 首先在Spdlog的github仓库中下载最新的源代码
git clone <https://github.com/gabime/spdlog.git>
- 进入源代码目录然后创建"build"目录
cd spdlog
mkdir build
cd build
- 依次在build目录下运行以下命令即可
cmake ..
make
sudo make install // 安装命令
Spdlog使用:
- 首先第一步就是在代码中加入spdlog的头文件。
#include<spdlog/spdlog.h>
- 之后就可以进行使用了
2、Spdlog代码编写
2.1、日志级别
- 对于一个系统来说,显示信息的级别非常关键,我们先来了解以下spdlog中的几种级别
trace = SPDLOG_LEVEL_TRACE // 最低级(用来记录代码执行轨迹)
debug = SPDLOG_LEVEL_DEBUG // (用来记录debug信息)
info = SPDLOG_LEVEL_INFO // 在上面的测试例子中用过
warn = SPDLOG_LEVEL_WARN
err = SPDLOG_LEVEL_ERROR
critical = SPDLOG_LEVEL_CRITICAL
off = SPDLOG_LEVEL_OFF // 最高级
注意:设定级别的作用就在于,你想在程序运行前指定我们需要记录哪些信息?我们可以自己控制日志记录的粒度。
2.2、控制台打印日志
- 方式一:最简单的使用方式,将日志输出到控制台
#include<iostream>
#include<spdlog/spdlog.h>
#include<spdlog/sinks/stdout_sinks.h>
void stdout_easy()
{
// 这个函数的意思其实就是根据参数“consoel”的名字在内部创建了一个logger,函数返回值就是这个logger的智能指针。
auto console = spdlog::stdout_color_mt("console");
// 所以我们可以直接通过智能指针调用名字对应“console”的logger对象的函数进行输出。
console->info("hello world");
}
int main()
{
stdout_easy();
return 0;
}
测试结果:
- 方式二:我们也可以使用
#include<iostream>
#include<spdlog/spdlog.h>
#include<spdlog/sinks/stdout_sinks.h>
void stdout_example()
{
// create a color multi-threaded logger
//spdlog::stdout_color_mt("console")函数创建一个名字为console的console logger,
//把这个logger注册到spdlog的全局注册表中,并且返回指向这个logger的指针(shared_ptr)
auto console_stdout = spdlog::stdout_color_mt("console");
// 如果我们不知道上方创建函数返回的指针,我们也可以直接通过logger名称来获取对应logger
// 通过get来获取已经创建好的logger,获取方式通过指针名,同时使用info函数输出日志内容
spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)");
}
int main()
{
stdout_example();
return 0;
}
运行结果:
2.3、文件保存日志
- 下面的案例将日志记录保存到文件中,以便于长久保存。
basic_logger_mt
只是spdlog中封装的其中一个日志记录器,还有很多其他的记录器。
#include<iostream>
#include<spdlog/spdlog.h>
int main()
{
// 创建一个名为basic_logger的日志记录器,并且返回指针。
// mt表示多线程,意味着多个线程中使用同一个日志记录器。
// 需要注意的是在文件内添加数据是通过追加的形式进行的,并且如果没有创建文件会自动创建文件。
auto basic_logger = spdlog::basic_logger_mt("basic_logger", "./logs/basic_log.txt");
for(int i=0; i<10000; i++)
{
basic_logger->info("Test file logger{}", i);
}
return 0;
}
运行结果:(成功保存了日志)
2.4、自己创建日志记录器
- 我们根据以上的学习之后,大体了解了其中的运行机制,所以我们就可以不使用他封装的日志记录器,而是使用他内部的logger对象封装适合自己的日志记录器。
- 需要注意的是,所谓的不同的日志记录器其实就是不同的logger对象,那么用什么来区分不同的logger对象呢,就是用创建logger对象时传入的参数sink对象,这个sink对象标记着不同的类型,把他作为参数logger会根据sink对象类型的不同创建不同的日志记录器。
- 这种方式有很大的用途,比如我们可以创建一个日志记录器,但是里边有多个类型的sink,从而可以一个logger能够实现多种效果。
#include<iostream>
#include<spdlog/spdlog.h>
#include<spdlog/sinks/sink.h>
void make_console_spdlog()
{
auto console_level = std::make_shared<spdlog::sinks::stdout_sink_mt>();
auto console = std::make_shared<spdlog::logger>("console", console_level);
console->info("hello world");
}
int main()
{
make_console_spdlog();
return 0;
}
测试结果:
注意:通过手动创建的日志记录器是无法通过名字在spdlog中进行查找的,因为他并没有注册到spdlog的全局注册表。
void make_console_spdlog()
{
auto console_level = std::make_shared<spdlog::sinks::stdout_sink_mt>();
auto console = std::make_shared<spdlog::logger>("console", console_level);
// 直接使用这种方式去获取日志记录器发生错误
spdlog::get("console")->info("hello world!");
}
测试结果:(会直接报错)
- 所以在创建日志记录器完成后,要将其注册到spdlog的全局注册表中
#include<iostream>
#include<spdlog/spdlog.h>
#include<spdlog/sinks/sink.h>
void make_console_spdlog()
{
auto console_level = std::make_shared<spdlog::sinks::stdout_sink_mt>();
auto console = std::make_shared<spdlog::logger>("console", console_level);
// 将创建的日志记录器注册到全局注册表中
spdlog::register_logger(console);
// 直接使用这种方式去获取日志记录器发生错误
spdlog::get("console")->info("hello world!");
}
int main()
{
make_console_spdlog();
return 0;
}
测试结果:
2.5、多记录器共享文件
- 我们也可以创建多个日志记录器,共享同一文件读写。
#include<iostream>
#include<spdlog/spdlog.h>
#include<spdlog/sinks/file_sinks.h>
void multi_file_write()
{
// 创建filesink,以便多个日志共享器共享。
auto filesink = std::make_shared<spdlog::sinks::simple_file_sink_mt>("./logs/multilogger.txt");
// 创建两个日志记录器来进行测试
auto onelogger = std::make_shared<spdlog::logger>("onelogger", filesink);
auto twologger = std::make_shared<spdlog::logger>("twologger", filesink);
// 注册进spdlog的全局注册表
spdlog::register_logger(onelogger);
spdlog::register_logger(twologger);
for(int i=0; i<100; i++)
{
onelogger->info("onelogger write:{}", i);
twologger->info("twologger write:{}", i);
}
}
int main()
{
multi_file_write();
return 0;
}
测试结果:(和预想的一样,交替在文件中打印)
2.6、日志分割
- 在我们的应用程序有很多日志需要记录时,每个日志文件就会变得非常大,这样就会影响应用程序的性能以及稳定性。所以为了解决这个问题,可以使用Spdlog的日志分割功能。
- rotating log 滚动日志,当日志文件超出规定大小时,会删除当前日志文件中所有内容,重新开始写入
#include<iostream>
#include<spdlog/spdlog.h>
void rotaing_file()
{
// 首先定义要使用分割文件的大小和数量
int max_size = 1048576*5;
int file_size = 3;
// 创建rotaing_logger_mt日志记录器
auto rotaing_file = spdlog::rotating_logger_mt("rotaing_file", "./logs/rotaingLogger.txt", max_size, file_size);
for(int i=0; i<100000; i++)
{
rotaing_file->info("rotaing file:{}", i);
}
}
int main()
{
rotaing_file();
return 0;
}
测试结果:(和预想一致)
2.7、定时日志
- Spdlog还可以实现定时创建文件存储日志的功能。
#include<iostream>
#include<spdlog/spdlog.h>
void daily_log()
{
// 创建daily_log日志
// 参数介绍 1日志记录器名称 2文件名 3时 4分
auto daily_log = spdlog::daily_logger_mt("daily_log", "./logs/daily_log.txt", 16, 18);
for(int i=0; i<100000; i++)
{
daily_log->info("daily log print {}", i);
}
}
int main()
{
daily_log();
return 0;
}
测试结果:如果程序不退出,每天下午4点18分会自动创建新的文件。
2.8、其他函数介绍
- 通过上面的学习,我们学会了spdlog其函数的基本使用方式,下面对spdlog中常用的一些函数和类进行介绍。
- level_enum:日志等级枚举,包括trace、info、debug、warn、err、critical、off、n_levels。
- sink:日志记录槽,进行底层操作(比如格式化内容,输出内容到控制台/文件)的类。spdlog自带的几种sinks已经能满足日常需求,也可以派生其基类设计新的sink以满足特殊的需求。sink类主要使用的函数包括:
- set_pattern(const std::string&):设置日志输出格式
- set_level(level_enum):设置日志输出的最低等级
- log(log_msg):由logger自动调用,外部不会主动调用
- logger:日志记录器,被顶层调用来输出日志的类。一个logger对象中存储有多个sink,当调用logger的日志输出函数时,logger会调用自身存储的所有sink对象的log(log_msg)函数进行输出。与自身的sink对应,spdlog也自带了几种logger。logger类主要使用的函数包括:
- set_pattern(const std::string&):设置logger包括的所有sink的日志输出内容格式。
- set_level(level_enum):设置logger日志输出最低等级,如果logger包含的sink没有设置日志等级的话,则会为其设置日志等级。
- log(level_enum level, log_msg content):按照level等级进行输出content,logger其中日志输出最低等级小于或等于level的sink会执行输出操作。
- info(content, arg1, arg2…):按照trace等级进行输出,输出内容由content与后面的采纳数格式化组成。荣磊的函数还包括debug/trace/warn…。
- st/mt:对象版本,spdlog中logger对象和sink对象都有两种版本,一种是以st结尾的单线程版本,以及以mt结尾的多线程版本。
- st:单线程版本,不用加锁,效率更高。
- mt:多线程版本,用于多线程程序是线程安全的。
2.9、记录器包含多个sink
- 像之前2.5多个记录器可以共享同一个文件sink,我们也可以将多个sink放在一个日志记录器logger中。
- 结合这个案例使用2.8介绍的函数。
#include<iostream>
#include<spdlog/spdlog.h>
#include<spdlog/sinks/stdout_sinks.h>
#include<spdlog/sinks/file_sinks.h>
void multi_sink()
{
// 创建两个sink,一个用于控制台输出,一个用于文件输出
auto console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
auto file_sink = std::make_shared<spdlog::sinks::simple_file_sink_mt>("./logs/multi_sink.txt");
// 设置两者输出等级
console_sink->set_level(spdlog::level::info);
file_sink->set_level(spdlog::level::debug);
// 将两种不同的sink放到一个日志输出器中
auto multi_sink = std::make_shared<spdlog::logger>("multi_sink", spdlog::sinks_init_list({console_sink, file_sink}));
multi_sink->set_level(spdlog::level::debug);
// 输出不同等级日志测试
multi_sink->info("hello world info");
multi_sink->debug("helllo world debug");
}
int main()
{
multi_sink();
return 0;
}
测试:
参考: