正确用法是让日志函数参数带默认值:void log(const char* msg, const std::source_location& loc = std::source_location::current());调用时写log("msg"),编译器自动注入真实位置;文件名需在调用点裁剪为短名,函数名不可靠应改用func,且source_location仅作代码锚点,不替代trace上下文。

直接用 std::source_location 打日志,跨平台分布式环境下大概率出现文件路径炸长、函数名为空或乱码、所有日志都指向同一行——不是你代码写错了,是默认用法没对齐编译器行为和部署约束。
为什么 log("msg") 总显示 logging.cpp:23 而不是真实业务行
因为 std::source_location::current() 是编译期求值的 consteval 函数,它“看到”的永远是它被写下的那一行。常见错误写法:
void log(const char* msg) { auto loc = std::source_location::current(); /* 这里固定返回 log 函数定义行 */ }-
#define LOG(m) log(m, std::source_location::current())→ 宏展开后current()位置塌缩到宏定义行 - 在封装层(如
Logger::write())里提前保存loc,后续调用复用它 → 位置固化在构造/初始化点
正确姿势只有一种:让参数带默认值,且调用时不传参。
void log(const char* msg, const std::source_location& loc = std::source_location::current());
调用时写 log("timeout"); 即可,编译器会在每一处调用点自动注入真实位置。
跨平台短文件名裁剪必须在调用点完成
loc.file_name() 返回的是绝对路径字符串,Linux/macOS 是 /home/user/src/core/db.cpp,Windows 是 C:projsrccoredb.cpp,直接输出会导致日志膨胀、grep 困难、服务端解析失败。
裁剪不能放在 logger 实现里统一做,否则分布式节点间路径结构不一致(CI 构建路径 vs 容器内挂载路径)。必须在每处调用点完成,确保所有节点拿到的都是 db.cpp 这种形式。
- 推荐用
std::filesystem::path(loc.file_name()).filename().c_str()—— 语义清晰、C++17 起可用、自动处理分隔符 - 若追求零开销且只跑 Linux/macOS:
strrchr(loc.file_name(), '/')后取子串;Windows 需额外判'\' - 禁止用
std::string构造再.substr(),高频日志下堆分配不可接受
function_name() 在 GCC/Clang/MSVC 上根本不可信
不同编译器对 loc.function_name() 的实现差异极大:
- GCC 默认返回空字符串(
"") - Clang 返回带签名的完整声明(
"void handle_request()") - MSVC 返回修饰名(
"?handle_request@@YAXXZ"),无法跨语言匹配
不能把它当 trace tag 或聚合维度用。替代方案是显式传入轻量标签:
#define LOG_TRACE(msg) log_trace(msg, std::source_location::current(), __func__)
注意:__func__ 是 C++11 标准,各平台行为一致,且比 function_name() 更稳定。
它不能替代 trace context,但能精准对齐已有链路
std::source_location 本身不含 trace_id、span_id、时间戳,也不支持序列化,不能用于跨进程注入。它的唯一正确定位是:作为已有分布式 trace 上下文的“代码锚点”。
例如使用 OpenTelemetry 时,日志函数应这样设计:
void log_with_trace(const char* msg, const opentelemetry::trace::SpanContext& ctx, const std::source_location& loc = std::source_location::current());
输出格式建议为:[0x1a2b3c][db.cpp:42] query timeout,其中 0x1a2b3c 来自 ctx.trace_id(),db.cpp:42 来自 loc。
最容易被忽略的一点:不要在日志函数内部再调用 opentelemetry::context::RuntimeContext::GetCurrent() 提取上下文——这会拿到当前线程的 context,而协程或异步回调中可能已丢失,必须由上层调用方显式传入。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











