spdlog跨模块调用时file sink无法写入日志文件问题
问题背景
使用spdlog-1.10.0版本(通过meson wrap file方式引入)
编写如下logger.cpp模块:
#include "spdlog/spdlog.h" #include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/stdout_color_sinks.h" #include <iostream> #include <memory> #include <vector> void create_logger() { std::string logger_name = "logger"; std::vector<spdlog::sink_ptr> sinks; auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); stdout_sink->set_level(spdlog::level::warn); sinks.push_back(stdout_sink); auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(".log"); file_sink->set_level(spdlog::level::level_enum::trace); sinks.push_back(file_sink); auto logger = std::make_shared<spdlog::logger>(logger_name, begin(sinks), end(sinks)); if (!logger) { std::cerr << "failed to create logger" << std::endl; } logger->set_level(spdlog::level::level_enum::trace); spdlog::register_logger(logger); } std::shared_ptr<spdlog::logger> get_logger(){ auto logger = spdlog::get("logger"); if(!logger){ std::cerr << "can't get logger" << std::endl; } return logger; } int main(){ create_logger(); get_logger()->debug("Dloading config"); get_logger()->error("Eloading config"); get_logger()->info("Iloading config"); get_logger()->trace("Tloading config"); get_logger()->warn("Wloading config"); }
直接编译运行该模块时结果符合预期:控制台仅输出warn及以上级别的error、warn日志,.log文件可正常记录所有trace及以上级别的全量日志,运行输出如下:
[2022-07-07 14:16:05.738] [logger] [error] Eloading config [2022-07-07 14:16:05.738] [logger] [warning] Wloading config $ cat .log [2022-07-07 14:16:05.738] [logger] [debug] Dloading config [2022-07-07 14:16:05.738] [logger] [error] Eloading config [2022-07-07 14:16:05.738] [logger] [info] Iloading config [2022-07-07 14:16:05.738] [logger] [trace] Tloading config [2022-07-07 14:16:05.738] [logger] [warning] Wloading config
异常现象:从其他模块调用get_logger()->info / trace写入日志时,仅控制台sink正常输出内容,日志文件无任何写入记录。
原因与修复方案
这个问题是spdlog使用中非常典型的配置/链接问题,按以下优先级排查修复即可:
- 首先补全日志flush规则配置
spdlog的file sink默认带输出缓冲区,不会每写一条日志就立刻刷到磁盘,默认仅在遇到error及以上级别日志、或者spdlog全局registry正常析构时才会执行flush。你在main函数中写入的日志能全部落盘,本质是main函数返回后spdlog全局单例正常析构,自动触发了缓冲区flush。如果其他模块的日志写入发生在main函数返回之后(比如静态变量析构阶段、动态库卸载回调阶段),此时spdlog的file sink可能已经被析构、或者缓冲区不会再被主动flush,就会出现日志丢在缓冲区没落盘的问题。
修复方式是在create_logger函数中注册完logger后,显式配置flush规则:// 全局配置:所有sink遇到trace及以上级别日志立刻刷盘 spdlog::flush_on(spdlog::level::trace); // 也可以单独给file sink配置刷盘级别,不影响控制台sink // file_sink->set_flush_level(spdlog::level::trace); - 排查多模块链接时的spdlog多实例问题
如果你的“其他模块”是独立的动态链接库(.so/.dll),且你把spdlog编译为静态库同时链接给主程序和动态库,会导致主程序和动态库各自持有一份独立的spdlog全局registry、独立的spdlog配置。此时主程序中注册的logger、绑定的file sink只存在于主程序的spdlog实例中,动态库中调用spdlog::get("logger")要么拿到空指针,要么拿到动态库自己spdlog实例里默认创建的logger(默认只带控制台sink),自然不会写入到你指定的.log文件中。
修复方式是把spdlog编译为动态链接库,让主程序和所有业务模块都链接同一个spdlog动态库,保证spdlog全局状态唯一。 - 规避生命周期问题
不要在静态变量析构、动态库卸载的阶段调用日志接口,这个阶段spdlog的全局单例、file sink的文件句柄可能已经被释放,会导致日志写入异常。如果必须在这些阶段输出日志,不要依赖spdlog全局registry管理logger生命周期,手动持有logger实例的shared_ptr,保证sink在日志写入完成前不会被析构。
另外你的代码中get_logger()在获取logger失败时会返回空指针,直接调用成员函数会触发段错误,建议补充判空逻辑后再使用。
内容的提问来源于stack exchange,提问作者CIsForCookies
相关产品推荐
相关产品推荐

