背景
在软件开发领域,日志记录是一项至关重要的工作。它不仅是程序运行情况的实时记录者,更是故障排查和系统优化的关键依据。然而,随着项目规模的扩大和复杂性的增加,传统的日志管理方式已经难以满足开发者的需求。这时候,一个高效、灵活的日志库就显得尤为重要。在众多的日志库中spdlog以其出色的性能和便捷的使用方式,赢得了广大开发者的青睐。
概述
spdlog,顾名思义,是一款追求速度和性能的日志库。它采用先进的异步日志处理技术,能够在不影响主程序性能的前提下,实现高效、实时的日志记录。同时,spdlog还提供了丰富的日志级别和灵活的日志格式设置,满足不同场景下的日志记录需求。
特性
spdlog的主要特性如下:
使用方法
下载和编译
源码下载:https://github.com/gabime/spdlog
spdlog为header only的日志库,无需编译,只需添加到项目中即可。
如果需要查看工程中的examples时,需要使用cmake进行编译,关于这部分的资料已经很多了,在此不再赘述。
初级示例
#include "spdlog/sinks/stdout_color_sinks.h"
// or #include "spdlog/sinks/stdout_sinks.h" if no colors needed.
void stdout_logger_example() {
// Create color multi threaded logger.
auto console = spdlog::stdout_color_mt("console");
// or for stderr:
// auto console = spdlog::stderr_color_mt("error-logger");
console->debug(__FUNCTION__);
console->info(__FUNCTION__);
}
int main()
{
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] \t[%P/%t] %v");
spdlog::set_level(spdlog::level::debug);
stdout_logger_example();
}
/*
output:
[2024-03-03 13:48:31.101] [debug] [17564/27436] stdout_logger_example
[2024-03-03 13:48:31.102] [info] [17564/27436] stdout_logger_example
*/
由于内部工厂函数不可见,导致看到如上代码时,并不能理解到spdlog的架构。整体架构如下
有上图可知,
1. spdlog拥有全局唯一的管理器(registry),registry用于管理和维护所有的logger实例。它提供了一系列的函数来创建、获取、删除logger实例,并支持对logger的全局配置。
2. 管理器中可以有多个日志记录器(logger/async_logger),日志记录器分为两类:同步日志记录和异步日志记录,logger是spdlog中的基本组件,用于记录日志消息。它提供了一系列的日志记录函数(如debug()、info()、error()等),以及设置日志级别、格式化输出等功能。logger可以输出到多个sink(日志记录器),如控制台、文件、syslog等。async_logger是从logger派生而来的一种特殊logger,用于实现异步的日志记录功能。它使用异步队列来缓冲日志消息,并通过后台线程将消息写入到指定的输出目标中,以提高性能和响应速度。
3. spdlog内有多个种类的日志记录器,日志记录器负责将日志消息发送到指定的输出目标。spdlog提供了多种内置的Sink,如stdout_sink、rotating_file_sink等,以支持不同的日志输出方式。开发者也可以自定义Sink,以满足特定的日志记录需求。
4. 日志格式化器用于将日志消息格式化为指定的字符串形式。可以通过为每个日志记录器(sink)自定义独有/共有的日志格式化器来定制日志消息的输出格式,包括时间格式、日志级别、线程ID等信息。
5. 异步日志记录线程池,负责从异步队列中取出日志消息,并将其写入到指定的输出目标中。它与async_logger配合工作,实现了异步的日志记录功能。。
进阶示例
本文将会结合实际的使用场景来展示spdlog的使用。
场景:在sdk开发中,sdk中的日志不仅需要保存到本地,同时希望通过回调函数的形式,将日志返回给sdk的调用者。日志信息形如“[年-月-日 时:分:秒.毫秒][日志等级][进程id/线程id] message”
分析如上场景可知,需要两个日志记录器,一个用于书写文件,一个用于回调;需要设置日志的格式化器。
常用的日志的格式化器标识符
flag | 释义 |
---|---|
%Y | 四位数的年 |
%m | 月 |
%d | 日 |
%H | 时 |
%M | 分 |
%s | 秒 |
%e | 毫秒 |
%n | 日志记录器的名字 |
%l | 日志等级 |
%L | 日志等级(短) |
%P | 进程ID |
%t | 线程ID |
%v | 待输出的日志消息 |
例:"[%Y-%m-%d %H:%M:%S.%e] [%P/%t] [%l] %v"
自定义回调类型的日志记录器
#ifndef MEMORYSINK_H
#define MEMORYSINK_H
#include<mutex>
#include "spdlog/sinks/base_sink.h"
#include "spdlog/details/log_msg.h"
namespace spdlog {
using memory_cb = std::function<void(const std::string&, size_t size)>;
namespace sinks{
template<typename Mutex>
class memory_sink final :public base_sink<Mutex> {
public:
explicit memory_sink(memory_cb callback) : callback_(callback) {}
protected:
void sink_it_(const spdlog::details::log_msg& msg) override {
spdlog::memory_buf_t formatted;
base_sink<std::mutex>::formatter_->format(msg, formatted);
size_t msg_size = formatted.size();
auto data = formatted.data();
std::string clean_log_msg(data, msg_size);
auto level = msg.level;
clean_log_msg.erase(std::remove_if(clean_log_msg.begin(), clean_log_msg.end(), [](unsigned char c) { return !isprint(c); }), clean_log_msg.end());
if (callback_) {
std::stringstream ss;
ss << clean_log_msg;
callback_(ss.str(), msg_size);
}
}
void flush_() override {}
private:
memory_cb callback_;
};
using memory_sink_mt = memory_sink<std::mutex>;
using memory_sink_st = memory_sink<details::null_mutex>;
}//namespace sinks
}//namespace spdlog
#endif//MEMORYSINK_H
使用多个日志记录器的样例代码
int main()
{
auto memory_sink = std::make_shared<spdlog::sinks::memory_sink_mt>(
[](const std::string &log_message, size_t data_size) {
std::cout << log_message << "\n"; // 此处简写,本应为回调函数给调用方
});
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("d://Garden_for_programmer.txt", true);
auto m_spdlog_log = std::make_shared<spdlog::logger>(
"wanos_log", spdlog::sinks_init_list{file_sink, memory_sink});
m_spdlog_log->set_level(spdlog::level::debug);
m_spdlog_log->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%P/%t] [%l] %v");
spdlog::register_logger(m_spdlog_log);//注册到管理类中
m_spdlog_log->debug("hello world");
m_spdlog_log->info("this is an info log");
}
总结
spdlog是一个功能强大且易于使用的C++日志库,它具有高性能、灵活性和线程安全等优点。同时,支持自定义日志记录器,极大的方便用户,且扩展了spdlog的使用场景。