接收器
- 接收器是实际将日志写入其目标的对象
- 每个接收器应该只负责单个目标(例如文件、控制台、数据库)
- 并且每个接收器都有自己的格式化程序对象的私有实例
spdlog
的接收器有_mt
(多线程)或_st
(单线程)后缀来指示线程安全
旋转接收器
- 当达到最大文件大小时,关闭文件,重命名它,然后创建一个新文件
- 最大文件大小和最大文件数都可以在构造函数中配置
1 2 3 4 |
// create a thread safe sink which will keep its file size to a maximum of 5MB and a maximum of 3 rotated files. #include "spdlog/sinks/rotating_file_sink.h" ... auto file_logger = spdlog::rotating_logger_mt("file_logger", "logs/mylogfile", 1048576 * 5, 3); |
1 2 3 4 |
#include "spdlog/sinks/rotating_file_sink.h" ... auto rotating = make_shared<spdlog::sinks::rotating_file_sink_mt> ("log_filename", 1024*1024, 5, false); auto file_logger = make_shared<spdlog::logger>("my_logger", rotating); |
每日接收器
1 2 3 |
#include "spdlog/sinks/daily_file_sink.h" .. auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily", 14, 55); |
简单文件接收器
1 2 3 |
#include "spdlog/sinks/basic_file_sink.h" ... auto logger = spdlog::basic_logger_mt("mylogger", "log.txt"); |
标准输出接收器,标准错误接收器
1 2 3 4 |
#include "spdlog/sinks/stdout_sinks.h" ... auto console = spdlog::stdout_logger_mt("console"); auto err_console = spdlog::stderr_logger_st("console"); |
1 |
auto sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); |
1 2 3 4 5 |
#include "spdlog/sinks/ostream_sink.h " ... std::ostringstream oss; auto ostream_sink = std::make_shared<spdlog::sinks::ostream_sink_mt> (oss); auto logger = std::make_shared<spdlog::logger>("my_logger", ostream_sink); |
空接收器
- 调试用
1 2 3 |
#include "spdlog/sinks/null_sink.h" ... auto logger = spdlog::create<spdlog::sinks::null_sink_st>("null_logger"); |
系统日志接收器
POSIX syslog(3)
接收器将其日志发送到 syslog
1 2 3 4 |
#include "spdlog/sinks/syslog_sink.h" ... auto syslog_sink = std::make_shared<spdlog::sinks::syslog_sink_mt>("my_ident", LOG_PID); auto syslog_logger = std::make_shared<spdlog::logger>("logger_name", syslog_sink); |
系统接收器
1 2 3 4 |
#include "spdlog/sinks/systemd_sink.h" ... auto systemd_sink = std::make_shared<spdlog::sinks::systemd_sink_st>(); auto systemd_logger = std::make_shared<spdlog::logger>("logger_name", systemd_sink); |
dist_sink
- 将日志消息分发到其他接收器列表
1 2 3 4 5 6 7 8 9 |
#include "spdlog/sinks/dist_sink.h" ... auto dist_sink = make_shared<spdlog::sinks::dist_sink_st>(); auto sink1 = make_shared<spdlog::sinks::stdout_sink_st>(); auto sink2 = make_shared<spdlog::sinks::simple_file_sink_st>("mylog.log"); dist_sink->add_sink(sink1); dist_sink->add_sink(sink2); |
msvc_sink
- Windows 调试接收器(使用 OutputDebugStringA 进行日志记录)
1 2 3 |
#include "spdlog/sinks/msvc_sink.h" auto sink = std::make_shared<spdlog::sinks::msvc_sink_mt>(); auto logger = std::make_shared<spdlog::logger>("msvc_logger", sink); |
dup_filter_sink
- 重复消息删除接收器。如果前一条消息相同且已过去的时间小于
max_skip_duration
,则跳过该消息
1 2 3 4 5 6 7 8 9 |
#include "spdlog/sinks/dup_filter_sink.h" auto dup_filter = std::make_shared<dup_filter_sink_st>(std::chrono::seconds(5)); dup_filter->add_sink(std::make_shared<stdout_color_sink_mt>()); spdlog::logger l("logger", dup_filter); l.info("Hello"); l.info("Hello"); l.info("Hello"); l.info("Different Hello"); |
环缓冲区接收器
- 环形缓冲区接收器将最新的日志消息保留在内存中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include "spdlog/sinks/ringbuffer_sink.h" auto ringbuffer_sink = std::make_shared<spdlog::sinks::ringbuffer_sink_mt>(128); std::vector<spdlog::sink_ptr> sinks; sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>("path/to/log.txt")); sinks.push_back(ringbuffer_sink); auto logger = std::make_shared<spdlog::logger>("logger_name", std::begin(sinks), std::end(sinks)); for (int i = 0; i < 256; ++i) { logger->info("Log message {}", i); } // Retrieve all log messages. `log_message` contains 128 messages. std::vector<std::string> log_messages = ringbuffer_sink->last_formatted(); // Retrieve a maximum of 64 log messages. std::vector<std::string> log_messages = ringbuffer_sink->last_formatted(64); |
qt_sink
- 可以从
QTextBrowser
、QTextEdit
输出日志消息
1 2 3 4 |
#include "spdlog/sinks/qt_sinks.h" auto logger = spdlog::qt_logger_mt("QLogger",ui->textBrowser); logger->info("hello QTextBrowser"); logger->warn("this msg from spdlog"); |
自定义接收器
- 推荐的方法是继承
base_sink
类。这个类已经处理线程锁定,并且使得实现线程安全接收器变得非常容易
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include "spdlog/sinks/base_sink.h" template<typename Mutex> class my_sink : public spdlog::sinks::base_sink <Mutex> { ... protected: void sink_it_(const spdlog::details::log_msg& msg) override { // log_msg is a struct containing the log entry info like level, timestamp, thread id etc. // msg.raw contains pre formatted log // If needed (very likely but not mandatory), the sink formats the message before sending it to its final destination: spdlog::memory_buf_t formatted; spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted); std::cout << fmt::to_string(formatted); } void flush_() override { std::cout << std::flush; } }; #include "spdlog/details/null_mutex.h" #include <mutex> using my_sink_mt = my_sink<std::mutex>; using my_sink_st = my_sink<spdlog::details::null_mutex>; |
- 创建后添加
1 2 3 4 5 6 |
inline std::vector<spdlog::sink_ptr> &spdlog::logger::sinks() { return sinks_; } spdlog::get("myExistingLogger")->sinks().push_back(myNewSink); |
记录器注册表
- spdlog 维护已创建记录器的全局(每个进程)注册表
- 目的是让记录器可以从项目中的任何地方轻松访问,而无需传递它们
- 如果未找到记录器,则返回空共享指针
1 2 3 4 5 6 7 |
spdlog::get("logger1")->info("hello"); .. .. some other source file.. .. auto l = spdlog::get("logger1"); l->info("hello again"); |
注册新的记录器
- 要注册手动创建的记录器(即不是由
spdlog.h
工厂函数创建的)
1 |
spdlog::register_logger(some_logger); |
重名冲突
- 当尝试使用注册表中已存在的名称进行注册时,
spdlog
将引发异常spdlog::spdlog_ex
从注册表中删除记录器
- drop函数可用于从注册表中删除记录器
- 如果不存在指向记录器的其他shared_ptr,则记录器将被关闭,并且其所有资源将被释放
1 2 3 |
spdlog::drop("logger_name"); //or remove them all spdlog::drop_all() |
异步日志记录
创建异步记录器
- 包含
#include spdlog/async.h
spdlog::async_factory
模板参数
1 2 3 4 5 6 7 |
#include "spdlog/async.h" void async_example() { // default thread pool settings can be modified *before* creating the async logger: // spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread. auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt"); } |
spdlog::create_async<Sink>
1 |
auto async_file = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_file_logger", "logs/async_log.txt"); |
spdlog::create_async_nb<Sink>
,创建一个永远不会阻塞完整队列的记录器
1 |
auto async_file = spdlog::create_async_nb<spdlog::sinks::basic_file_sink_mt>("async_file_logger", "logs/async_log.txt"); |
- 直接构造并使用全局线程池
1 2 |
spdlog::init_thread_pool(queue_size, n_threads); auto logger = std::make_shared<spdlog::async_logger>("as", some_sink, spdlog::thread_pool(), async_overflow_policy::block); |
- 直接构造并使用自定义线程池
1 2 |
auto tp = std::make_shared<details::thread_pool>(queue_size, n_threads); auto logger = std::make_shared<spdlog::async_logger>("as", some_sink, tp, async_overflow_policy::block); |
满队列策略
- 当队列已满时,有两种选择:
- 阻止呼叫者,直到有更多空间(默认行为)
- 删除队列中最旧的消息并用新消息替换,而不是等待更多空间。使用
create_async_nb
工厂函数或spdlog::async_overflow_policy
记录器的构造函数中的
1 2 3 |
auto logger = spdlog::create_async_nb<spdlog::sinks::basic_file_sink_mt>("async_file_logger", "logs/async_log.txt"); // or directly: auto logger = std::make_shared<async_logger>("as", test_sink, spdlog::thread_pool(), spdlog::async_overflow_policy::overrun_oldest); |
spdlog的线程池
- 默认情况下,spdlog 创建一个队列大小为 8192 的全局线程池和 1 个工作线程,为所有异步记录器提供服务
- 所有队列槽位均在线程池构造中预先分配(每个槽位在 64 位系统上占用约 256 字节)
- 线程池大小和线程可以通过以下方式重置
- 这将删除旧的全局线程池(tp)并创建一个新的 tp - 这意味着任何使用旧 tp 的记录器将停止工作,因此建议在创建任何异步记录器之前调用它
1 |
spdlog::init_thread_pool(queue_size, n_threads); |
- 如果不同的记录器必须有单独的队列,则可以创建不同的池实例并将它们传递给记录器
1 2 3 4 5 |
auto tp = std::make_shared<details::thread_pool>(128, 1); auto logger = std::make_shared<async_logger>("as", some_sink, tp, async_overflow_policy::overrun_oldest); auto tp2 = std::make_shared<details::thread_pool>(1024, 4); // create pool with queue of 1024 slots and 4 backing threads auto logger2 = std::make_shared<async_logger>("as2", some_sink, tp2, async_overflow_policy::block); |
- 线程池有额外的构造函数,它们将回调作为参数。这些将由每个线程在创建之后和销毁之前执行
1 2 3 4 5 6 7 |
std::function<void()> on_start = []() { /* execute on start */ }; auto on_stop = []() { /* execute on stop */ }; auto tp = std::make_shared<details::thread_pool>(1024, 1, on_start, on_stop); // init_thread_pool helper also supports optional on_start and on_stop callbacks spdlog::init_thread_pool(1024, 1, on_start); spdlog::init_thread_pool(1024, 1, on_start, on_stop); |
注意
- 默认情况下,spdlog 创建一个工作线程,它保留队列中消息的顺序
- 对于具有多个工作线程的线程池,消息可能会在出队后重新排序
- 如果要保持消息顺序,则在线程池中只创建一个工作线程
刷新策略
- 默认情况下,spdlog 会在认为合适时让底层 libc 刷新,以实现良好的性能
手动刷新
- 可以使用该
logger->flush()
函数指示记录器刷新其内容。记录器将依次调用flush()
每个底层接收器上的函数 - 如果使用异步记录器,
logger->flush()
请将消息发布到请求刷新操作的队列,以便该函数立即返回
基于级别的自动刷新
- 可以设置触发自动刷新的最低日志级别
- 每当记录错误或更严重的消息时,这将触发刷新
1 |
my_logger->flush_on(spdlog::level::err); |
基于间隔的定期刷新
spdlog
支持设置刷新间隔。这是由单个工作线程实现的,该线程定期在每个记录器上调用flush()
- 需要注意的是,仅在线程安全记录器上使用此选项,因为定期刷新发生在不同的线程中
1 |
spdlog::flush_every(std::chrono::seconds(5)); |
默认记录器
- spdlog 创建一个默认的全局记录器(到标准输出、彩色和多线程)
- 直接调用即可轻松使用
spdlog::info(..), spdlog::debug(..), etc
- 它的实例可以替换为任何其他记录器(shared_ptr)
1 2 |
spdlog::set_default_logger(some_other_logger); spdlog::info("Use the new default logger"); |
异常相关
Spdlog
在记录时不会抛出异常(从版本39cdd08开始)- 如果在记录期间发生错误,库将向 stderr 打印一条错误消息
- 为了避免屏幕上充斥着错误消息,每个记录器的速率限制为 1 条消息/分钟
- 它可能会在记录器或接收器的建造过程中抛出,因为它被认为是致命的
更改全局错误处理
1 2 3 |
spdlog::set_error_handler([](const std::string& msg) { std::cerr << "my err handler: " << msg << std::endl; }); |
对于特定的记录器
1 2 3 |
critical_logger->set_error_handler([](const std::string& msg) { throw std::runtime_error(msg); }); |
默认错误处理
_default_err_handler
将使用它来打印错误
1 |
fmt::print(stderr, "[*** LOG ERROR ***] [{}] [{}] {}\n", date_buf, name(), msg); |
动态库中使用spdlog
问题
- 由于 spdlog 仅包含标头,因此构建共享库并在主程序中使用它不会在它们之间共享注册表
- 这意味着对类似函数的调用
spdlog::set_level(spdlog::level::level_enum::info)
不会更改 DLL 中的记录器
解决
- 在两个注册表中注册记录器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// mylibrary.h // In library, we skip the symbol exporting part #include <memory> #include <vector> #include <spdlog/spdlog.h> #include <spdlog/logger.h> #include <spdlog/sinks/stdout_color_sinks.h> namespace library { static const std::string logger_name = "example"; std::shared_ptr<spdlog::logger> setup_logger(std::vector<spdlog::sink_ptr> sinks) { auto logger = spdlog::get(logger_name); if(not logger) { if(sinks.size() > 0) { logger = std::make_shared<spdlog::logger>(logger_name, std::begin(sinks), std::end(sinks)); spdlog::register_logger(logger); } else { logger = spdlog::stdout_color_mt(logger_name); } } return logger; } void test(std::string message) { auto logger = spdlog::get(logger_name); if(logger) { logger->debug("{}::{}", __FUNCTION__, message); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// In the main program #include <mylibrary.h> #include <spdlog/logger.h> #include <spdlog/sinks/daily_file_sink.h> #include <spdlog/sinks/stdout_sinks.h> int main() { // We assume that we load the library here ... // Let's use the library std::vector<spdlog::sink_ptr> sinks; sinks.push_back(std::make_shared<spdlog::sinks::stdout_sink_st>()); sinks.push_back(std::make_shared<spdlog::sinks::daily_file_sink_st>("logfile", 23, 59)); auto logger = library::setup_logger(sinks); spdlog::set_level(spdlog::level::level_enum::debug); // No effect for the library. library::test("Hello World!"); // No logging spdlog::register_logger(logger); // Now this will also affect the library logger spdlog::set_level(spdlog::level::level_enum::debug); library::test("Hello World!"); // Hurray ! return 0; } |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ C++_多态、类型转换、数据段、BSS段、类型视图06/21
- ♥ C++并发编程 _ 基于锁的数据结构08/19
- ♥ STL_list05/04
- ♥ Deelx正则引擎使用12/24
- ♥ Boost 程序库完全开发指南:函数并发08/25
- ♥ C++_可以重载的运算符12/22