日志乱码主因是c++流未绑定locale:windows需std::locale::global(std::locale(“”)),linux/macos容器中要setenv(“lang”,”en_us.utf-8″,1);统一用utf-8 std::String而非wchar_t;windows控制台输出前调setconsoleoutputcp(cp_utf8);spdlog传utf-8字符串并避用wfile_sink_mt;格式化必用fmt::format防截断。

日志输出乱码,是因为没设 locale
默认 std::cout 和 std::ofstream 不处理 UTF-8 字节流,中文、日文写进去就是问号或方块。不是编码选错了,是 C++ 流没绑定本地化环境。
- Windows 下必须调用
std::locale::global(std::locale("")),否则std::wcout也无效 - Linux/macOS 一般默认生效,但若容器里运行(如 Alpine),
LANG环境变量为空,得手动setenv("LANG", "en_US.UTF-8", 1)再构造 locale - 别用
std::locale::global(std::locale("zh_CN.UTF-8"))—— 这个名字在 macOS 上不存在,会抛std::runtime_error
用 wchar_t 日志还是 UTF-8 char 日志?
选 UTF-8 char 更稳妥。C++ 标准库对 wchar_t 的跨平台支持很弱:Windows 用 UTF-16,Linux/macOS 用 UTF-32,日志文件一换系统就打不开。
- 所有日志字符串统一用
std::string存 UTF-8 编码(比如"用户登录失败"直接硬编码,编辑器保存为 UTF-8) - 写文件时用
std::ofstream,**不加**.imbue()—— 默认二进制写入,UTF-8 字节原样落地 - 控制台输出前,Windows 需调
SetConsoleOutputCP(CP_UTF8);Linux/macOS 无需额外操作
第三方日志库(如 spdlog)怎么开多语言
spdlog 默认不处理宽字符,直接传 UTF-8 std::string 就行,但有两个坑:
- 如果用了
spdlog::stdout_color_mt(),Windows 控制台默认不显示 UTF-8,得在创建 logger 前调SetConsoleOutputCP(CP_UTF8) - 用
spdlog::rotating_logger_mt写文件时,确保文件名也是 UTF-8 字符串(比如"日志_2024.log"),否则 Windows 下CreateFileA会截断 - 别碰
spdlog::wfile_sink_mt—— 它内部用std::wofstream,又绕回 locale 和宽字符兼容性问题
格式化时插入变量,中文被截断怎么办
用 fmt::format(spdlog 4.0+ 内置)最安全。自己拼接 std::string + std::to_string 没问题,但用 std::sprintf 或旧式 snprintf 会崩 —— 它们按字节算长度,UTF-8 中文占 3 字节,一个汉字当三个字符切,后面全乱。
立即学习“C++免费学习笔记(深入)”;
- 始终用
fmt::format("用户 {} 登录失败,错误码 {}", user_name, code),user_name是 UTF-8std::string - 避免
std::ostringstream+拼接含中文的 <code>const char*,某些编译器(如 MSVC 低版本)对非 ASCII 字面量处理不一致 - 如果必须用 C 风格格式化,改用
std::vsnprintf并传入足够大的缓冲区(比如 4096 字节),别信“256 肯定够”
真正麻烦的不是“怎么打中文”,是“什么时候 locale 生效、什么时候不生效”。同一个 std::ofstream 实例,构造前设 locale 有效,构造后再 imbue 基本无效 —— 这类细节不试一次根本记不住。