日志的作用
在程序运行过程中打印日志,可以帮助我们跟踪调试程序的运行状态,获取需要的调试信息。通过日志打印的时间,我们可以了解程序在特定时间的行为,分析性能瓶颈,甚至有大佬可以预测潜在的系统故障。
什么是spdlog
spdlog是一款开源的C++日志库,他有着极高的性能和几乎零成本的抽象。spdlog支持异步和同步日志记录,提供了多种日志级别,可以将日志打印在终端,文件或者自定义接收器(如数据库)。
git clone https://github.com/gabime/spdlog.git
cd spdlog
mkdir build & cd build
cmake ..
make
sudo make install
spdlog为什么高效
- 零成本的抽象:spdlog通过模版和内联函数,确保只有在真正需要的时候才进行日志记录。(内联函数在需要的时候会直接编译在程序中,模版只有在我们需要的时候才会展开)
- 异步日志记录:spdlog支持异步日志记录,这意味着他可以将消息发送到线程池进行处理,不会阻塞当前线程,减少了主线程的开销。
- 高效的格式化:spdlog使用
fmt
库进行高效的字符串格式化,减少格式化的时间。
spdlog的特征
- 高速的日志记录:spdlog可以每秒处理百万条日志消息。
- 低内存占用:spdlog的设计确保在高负载下依然保持低内存占用。
- 灵活的配置:用户可以根据需要配置spdlog,选择异步(mt)或者同步(st),选择不同的日志级别和输出目标。
spdlog的输出控制
- 多种日志级别:spdlog的日志级别包括:trace,debug,info,warn,error和critical。
- 多目标:可以将日志输出到终端,文件和接收器(数据库、远程服务器等)。
- 格式化输出:fmt支持高调的格式化输出
spdlog处理流程
spdlog的不同组件负责不同的部分。registry负责管理所有的组件,logger负责记录日志,sink负责将日志输出到指定位置,formatter将日志转化为特定格式,Async Logger可以异步将日志消息写入sink。
spdlog使用方法
使用默认logger打印输出
#include <chrono>
#include <iostream>
#include <memory>
#include <spdlog/async.h>
#include <spdlog/async_logger.h>
#include <spdlog/common.h>
#include <spdlog/logger.h>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
int main()
{
spdlog::trace("hello trace");
spdlog::debug("hello debug");
spdlog::info("hello info");
spdlog::warn("hello warn");
spdlog::error("hello error");
spdlog::critical("hello critical");
return 0;
}
我们查看这些接口的源代码实现如下:
template <typename T>
inline void trace(const T &msg) {
default_logger_raw()->trace(msg);
}
template <typename T>
inline void debug(const T &msg) {
default_logger_raw()->debug(msg);
}
template <typename T>
inline void info(const T &msg) {
default_logger_raw()->info(msg);
}
template <typename T>
inline void warn(const T &msg) {
default_logger_raw()->warn(msg);
}
template <typename T>
inline void error(const T &msg) {
default_logger_raw()->error(msg);
}
template <typename T>
inline void critical(const T &msg) {
default_logger_raw()->critical(msg);
}
我们上面说过,logger是负责记录日志消息的,在spdlog中存在一个默认logger可以直接使用,不需要我们进行配置。通过上图的输出可以看出来:默认的logger只会输出info
级别及其以上级别的日志。
通过工厂模式接口配置logger
int main()
{
auto logger = spdlog::stdout_color_mt<spdlog::async_factory>("console");
logger->set_pattern("***[%H:%M:%S %z][thread %t] %v***");
logger->info("logger info");
spdlog::get("console");
spdlog::get("console")->error("logger error");
return 0;
}
这边我们自己定义了一个logger,对他进行了输出的格式化,并且将已经打印的日志get
出来,重新打印输出。这里之所以可以get出来是因为:从上面的结构图可以看出,logger
是归registry
管理的,我们通过单例registry将logger找到,换句话说,每一个logger在创建时,都是向registry注册。
源码接口:
namespace spdlog {
template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name,
color_mode mode) {
return Factory::template create<sinks::stdout_color_sink_mt>(logger_name, mode);
}
template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stdout_color_st(const std::string &logger_name,
color_mode mode) {
return Factory::template create<sinks::stdout_color_sink_st>(logger_name, mode);
}
template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name,
color_mode mode) {
return Factory::template create<sinks::stderr_color_sink_mt>(logger_name, mode);
}
template <typename Factory>
SPDLOG_INLINE std::shared_ptr<logger> stderr_color_st(const std::string &logger_name,
color_mode mode) {
return Factory::template create<sinks::stderr_color_sink_st>(logger_name, mode);
}
}
//这里是通过工厂模式的接口,有mt(异步)和st(同步)的接口。
SPDLOG_INLINE std::shared_ptr<logger> get(const std::string &name) {
return details::registry::instance().get(name);
}
//从这里可以看出,单例registry可以get到logger
配置格式化打印(logger层面)
logger->set_pattern("***[%H:%M:%S %z][thread %t] %v***");
这一行就是配置格式化打印,配置方法可以参考文档中的表格:
配置输出位置sink & 配置格式化打印(sink层面)
class my_formatter_flag : public spdlog::custom_flag_formatter
{
public:
void format(const spdlog::details::log_msg &, const std::tm &, spdlog::memory_buf_t &dest) override
{
std::string some_text = "lenn-flag";
dest.append(some_text.data(), some_text.data() + some_text.size());
}
std::unique_ptr<spdlog::custom_flag_formatter> clone() const override
{
return spdlog::details::make_unique<my_formatter_flag>();
}
};
int main()
{
auto logger1 = std::make_shared<spdlog::logger>(std::string("console1"));
auto sink1 = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
auto sink2 = std::make_shared<spdlog::sinks::basic_file_sink_mt>("lenn.log");
auto formatter = std::make_unique<spdlog::pattern_formatter>();
formatter->add_flag<my_formatter_flag>('*').set_pattern("[%n] [%*] [%^%l%$] %v");
sink1