You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 18:09:38