所有日志显示固定行号是因为std::source_location::current()被放在封装函数内而非调用现场;唯一可靠方案是预处理器宏在调用点直接展开,避免中间层、提前求值和运行时路径裁剪。

在自动化构建系统(如 Bazel、CMake+Ninja)中用 std::source_location 打日志,若发现所有日志都显示 build.cpp:15 或固定在某个封装文件里,说明位置信息已漂移——这不是构建系统的问题,而是 std::source_location 被错误地放在了中间层函数体内或转发调用链中。
为什么构建系统日志总显示封装函数的行号
根本原因是:你在 BuildLogger::log() 这类封装函数内部写了 std::source_location::current(),它就永远返回该函数定义处的位置。构建脚本调用的是 logger::info("compiling target"),但这个调用可能经过 BuildTask::run() → LoggerProxy::write() → BaseLogger::log() 多层转发,每层都会截断原始调用点。
- 模板函数、
std::function回调、lambda 包装器都会让std::source_location::current()指向它们自身,而非业务代码 - 即使启用了 LTO,只要调用链中存在非内联函数,位置信息就会上浮到那一层
-
spdlog::logger::info()等现成接口若未重载带std::source_location的版本,直接传参会失败或退化为宏 fallback
构建系统中必须用宏展开捕获真实调用点
函数默认参数方案在构建系统里极易失效,因为构建逻辑常跨编译单元、带模板推导、或被 CMake 的 add_compile_options(-fno-inline) 干扰。唯一可靠方式是预处理器宏,在调用现场生成 std::source_location::current()。
- 正确写法:
#define BUILD_LOG_INFO(msg) ::build::detail::log_impl((msg), std::source_location::current()) - 宏必须展开后直接调用
std::source_location::current(),不能包裹在另一层函数调用里(如log_wrapper(std::source_location::current())) - 避免在宏里做
std::string构造、路径裁剪或格式化——构建过程高频打日志,隐式分配会拖慢 CI - 若需支持可变参数(如
BUILD_LOG_INFO("failed: %s", err.c_str())),宏要用__VA_ARGS__并转给fmt::format或std::vformat,不要在宏里拼接字符串字面量
跨平台构建中 file_name() 路径裁剪必须在调用点完成
std::source_location::file_name() 返回绝对路径(如 /home/ci/workspace/myproj/src/compile_task.cpp),而不同 CI 环境路径结构差异大,硬编码裁剪逻辑会出错。裁剪动作必须发生在日志真正被构造的那一刻,且不能依赖 std::filesystem(C++17 且有运行时开销)。
- 推荐轻量方案:
const char* short_file = strrchr(loc.file_name(), '/'); short_file = short_file ? short_file + 1 : loc.file_name(); - Windows 下补一句:
#ifdef _WIN32 short_file = strrchr(loc.file_name(), '\'); #endif - 绝对不要在日志函数里用
std::string(loc.file_name()).substr(...)—— 构建系统日志量大,堆分配不可接受 - 如果构建脚本本身由 Python/Bazel rule 驱动,可在生成 C++ 源码时注入相对路径宏(如
-DBUILD_REL_PATH="src/compile_task.cpp"),绕过运行时裁剪
与 OpenTelemetry trace 上下文对齐的关键约束
std::source_location 本身不携带 trace ID,但在构建系统中配合 OpenTelemetry SDK 使用时,必须确保两者在同一调用点注入,否则日志和 trace 无法行级对齐。
- 定义内联函数:
inline void build_log_with_trace(const char* msg, const opentelemetry::trace::SpanContext& ctx, const std::source_location& loc = std::source_location::current()) - 调用时只传前两个参数:
build_log_with_trace("link failed", current_span_context());手动传第三个参数会导致全部日志挤在头文件某一行 - 禁止把
loc提前赋值为局部变量、静态变量或类成员——一旦构造完成,位置就固化了 - CI 节点上若启用
-flto=thin,仍能保持准确,前提是函数签名中显式声明默认参数,且编译器未优化掉调用点语义(GCC 12+、Clang 14+ 已稳定支持)
真正难的不是怎么写 std::source_location::current(),而是守住“调用点”这个边界——任何中间层、任何提前求值、任何跨平台路径处理,都可能让日志从精准锚点变成无效噪音。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











