spdlog是一款追求速度和性能的日志库。采用先进的异步日志处理技术,能够在不影响主程序性能的前提下,实现高效、实时的日志记录。同时,spdlog还提供了丰富的日志级别和灵活的日志格式设置,满足不同场景下的日志记录需求。 spdlog为header only的日志库,无需编译,只需添加到项目中即可。如果需要查看工程中的examples时,需要使用cmake进行编译。spdlog的主要特性如下:
高性能:采用异步日志处理技术,将日志记录操作与主程序运行分离,从而避免了日志记录对程序性能的影响。
灵活的日志级别:支持多种日志级别,如DEBUG、INFO、WARN、ERROR等,方便开发者根据不同的需求选择合适的日志级别。
可定制的日志格式:开发者可以根据自己的喜好和项目需求,定制个性化的日志格式,使日志输出更加清晰易读。
强大的扩展性:SPDLOG支持多种输出目标,如文件、控制台、网络等,同时还可以通过插件机制实现与其他系统的集成。
跨平台:在几乎所有支持C++编译器的平台上运行
spdlog地址:https://github.com/gabime/spdlog
spdlog::set_level(spdlog::level::debug);
spdlog::default_logger()->debug("hello world");
auto console_sink=std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_level(spdlog::level::warn);
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/multisink.txt", true);
file_sink->set_level(spdlog::level::debug);
spdlog::logger logger("multi_sink",{console_sink,file_sink});
logger.set_level(spdlog::level::debug);
logger.warn("this should appear in both console and file");
logger.info("this message should not appear in the console, only in the file");
spdlog::set_default_logger(logger);
auto roating_logger=spdlog::rotating_logger_mt("logger","logs/rotating.txt",1048576*5,3);
auto daily_logger=spdlog::daily_logger_mt("daily_logger","logs.daily.txt",2,30);
spdlog::default_logger()->debug("{}","hello world");
spdlog::default_logger()->debug("{0},{1},{2}",'a','b',c');//Result:"a,b,c"
spdlog::default_logger()->debug("{},{},{}",'a','b','c');//Result:"a,b,c"
spdlog::default_logger()->debug("{2},{1},{0}",'a','b','c');//Result:"c,b,a"
spdlog::default_logger()->debug("{0}{1}{0}","abra", "cad"); // arguments'indices can be repeated
spdlog::default_logger()->debug("{:<30}","left aligned");//Result:"left aligned
spdlog::default_logger()->debug("{:>30}","right aligned");//Result:"right aligned"
spdlog::default_logger()->debug("{:^30}","centered");// Result:"centered
spdlog::default_logger()->debug("{:*^30}","centered");//use'*'as a fill char
//Result:"**********centered*********"
using namespace fmt::literals;
spdlog::default_logger()->debug("Elapsed time: {s:.2f} seconds","s" a=1.23);
在软件开发领域,日志记录是一项至关重要的工作。它不仅是程序运行情况的实时记录者,更是故障排查和系统优化的关键依据。然而,随着项目规模的扩大和复杂性的增加,传统的日志管理方式已经难以满足开发者的需求。这时候,一个高效、灵活的日志库就显得尤为重要。
#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配合工作,实现了异步的日志记录功能。。
场景:在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");
}
15. spdlog
spdlog 是一个现代 C++ 日志库,提供了高性能、多线程安全的日志记录功能。它支持多种日志级别、多种日志输出目标(如控制台、文件、网络等),并且易于集成到现有的 C++ 项目中。spdlog 提供了简洁的 API 和灵活的配置选项,使得开发者能够轻松地记录和管理应用程序的日志信息。
15.1 使用场景
-
应用程序日志:在开发过程中记录调试信息、警告和错误信息,以便快速排查和修复问题。
-
性能分析:记录程序的运行时间、内存占用等性能指标,用于分析和优化程序性能。
-
数据采集:将程序输出的数据记录到日志文件中,用于后续分析和处理。
-
分布式系统:在分布式系统中记录节点间的通信和状态信息,用于监控和故障排查。
15.2 代码示例
以下是一个经典的使用 spdlog 的简单示例,演示了如何在 C++ 项目中记录日志信息到控制台和文件:
#include <iostream>
#include "spdlog/spdlog.h"
int main() {
// 初始化日志记录器,创建一个控制台和一个文件输出目标
auto console_logger = spdlog::stdout_logger_mt("console");
auto file_logger = spdlog::basic_logger_mt("file", "example.log");
// 设置日志级别
console_logger->set_level(spdlog::level::debug);
file_logger->set_level(spdlog::level::info);
// 记录日志信息
console_logger->info("Logging to console");
file_logger->debug("Logging to file");
return 0;
}
在这个例子中:
-
我们首先包含了 spdlog 头文件,并创建了一个控制台和一个文件输出目标的日志记录器。
-
使用 set_level 方法设置了日志记录器的日志级别,控制台日志记录器的级别为 debug,文件日志记录器的级别为 info。
-
使用 info 和 debug 方法记录了一些日志信息,分别输出到控制台和文件中。
总结
spdlog是一个功能强大且易于使用的C++日志库,它具有高性能、灵活性和线程安全等优点。同时,支持自定义日志记录器,极大的方便用户,且扩展了spdlog的使用场景。
高频交易中常用的日志库——Quill
日志作为软件中一个非常重要的模块,开发者总是希望记录的信息越全越好,记录日志使用的性能越少越好。所以出现了很多优秀的日志库,比如spdlog、log4cpp、log4cplus等。
Quill是一个高性能的C++日志库,它提供了强大的日志记录功能和灵活的配置选项。其主要特性如下:
-
高性能:Quill采用了异步日志处理机制,将日志的格式化和写入操作交给后台线程来完成,这样就不会阻塞主线程的执行。这种设计使得程序的响应速度不受日志记录的影响,保证了应用程序的高性能。特别是在高并发和实时性要求较高的场景下,Quill的优势尤为明显。
-
异步处理:Quill的异步处理机制是其核心特性之一。它通过后台线程来处理日志的格式化和I/O操作,确保主线程始终保持响应式,不会因为日志记录而产生延迟。这种设计不仅提高了性能,还使得日志记录变得更加灵活和高效。
-
最小头文件包含:Quill的设计非常注重轻量化,前端仅需要包含
Logger.h
和LogMacros.h
两个头文件,用于日志记录。这种最小化头文件依赖的设计,使得Quill非常易于集成到现有项目中,减少了不必要的复杂性和依赖项。 -
灵活的日志级别:Quill支持多种日志级别,包括DEBUG、INFO、WARNING、ERROR和FATAL。开发者可以根据需要选择合适的日志级别,以便在开发和生产环境中进行不同的日志记录。这种灵活的日志级别设置,使得日志管理更加方便和高效。
-
丰富的日志格式:Quill提供了丰富的日志格式选项,包括时间戳、线程ID、日志级别、文件名、行号等。开发者可以根据需要自定义日志格式,以满足不同的日志记录需求。这种灵活的日志格式设置,使得日志输出更加清晰和易于分析。其支持的字段见后文。
-
编译时优化:Quill支持在编译时消除特定的日志级别,这意味着开发者可以根据不同的场景裁剪日志级别,从而减少不必要的开销。这种编译时优化不仅提高了性能,还使得日志记录更加灵活和高效。
-
时间戳排序:在多线程应用程序中,日志的顺序对于调试至关重要。Quill能够确保日志按时间顺序排列,简化了多线程环境下的调试过程。这种时间戳排序的特性,使得开发者可以更方便地追踪事件的顺序和流程。
-
日志过滤:Quill支持日志过滤功能,开发者可以根据需要只处理相关的日志消息。这种过滤机制不仅提高了日志记录的效率,还减少了不必要的日志信息,使得日志更加聚焦和有用。
Quill支持输出的字段有:
格式化选项 | 中文解释 |
---|---|
%(time) | 表示日志语句创建时的人类可读时间戳。 |
%(file_name) | 发出日志调用的源文件的文件名。 |
%(full_path) | 发出日志调用的源文件的完整路径。 |
%(caller_function) | 包含日志调用的函数的名称。 |
%(log_level) | 日志消息的日志级别的文本表示。 |
%(log_level_short_code) | 缩写的日志级别名称。 |
%(line_number) | 发出日志调用的源文件中的行号。 |
%(logger) | 用于记录日志调用的日志记录器的名称。 |
%(message) | 日志消息本身。 |
%(thread_id) | 发出日志调用的线程的ID。 |
%(thread_name) | 线程的名称。必须在该线程上的第一个日志语句之前设置。 |
%(process_id) | 发出日志调用的进程的ID。 |
%(source_location) | 作为单个字符串的完整源文件路径和行号。 |
%(short_source_location) | 作为单个字符串的缩短后的源文件名和行号。 |
%(tags) | 当使用_TAGS宏时附加到消息上的其他自定义标签。 |
%(named_args) | 附加到消息上的键值对。仅在消息具有命名参数时适用;否则为空。 |
安装
Quill具有最小头包含的特性,所以只需要包含头文件即可使用。
https://github.com/odygrd/quill.git下载源码,然后将其添加到你的项目中。
展示如何创建日志记录器并记录日志:
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/sinks/ConsoleSink.h"
#include "quill/sinks/FileSink.h"
int main()
{
quill::Backend::start();
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("console_sink");
auto file_sink = quill::Frontend::create_or_get_sink<quill::FileSink>("d://file_sink.log");
quill::Logger* uniform_logger = quill::Frontend::create_or_get_logger(
"uniform_logger",
{console_sink, file_sink},
quill::PatternFormatterOptions{
"%(time)[%(process_id)/%(thread_id)] [%(log_level)] %(message)",
"%Y_%M_%d %H:%M:%S:%Qms",
quill::Timezone::GmtTime
});
uniform_logger->set_log_level(quill::LogLevel::Debug);
LOG_DEBUG(uniform_logger, "This is a debug message that only goes to file");
LOG_INFO(uniform_logger, "This is an info message that goes to console {}", 123);
LOG_INFO(uniform_logger, "This is an info message that goes to file {}", 456);
quill::Backend::stop();
return0;
}