必须在每次输出前检查当前级别是否允许该消息打印;定义enum class LogLevel { DEBUG, INFO, ERROR };持有一个全局LogLevel变量,如static LogLevel g_log_level = LogLevel::INFO;每次日志调用前加if (level <= g_log_level)判断。

用 std::ostream + 枚举控制日志级别最轻量
不依赖第三方库时,直接用 C++ 标准流配合一个日志级别枚举就能跑起来。关键不是“怎么封装”,而是“怎么让级别真正起作用”——必须在每次输出前检查当前级别是否允许该消息打印。
常见错误是把级别当装饰用(比如只在字符串开头加个 [DEBUG]),结果 DEBUG 日志照常刷屏,完全没过滤。
- 定义级别枚举:
enum class LogLevel { DEBUG, INFO, ERROR }; - 全局或类内持有一个
LogLevel变量,比如static LogLevel g_log_level = LogLevel::INFO; - 每个日志调用前加判断:
if (level - 注意比较顺序:
DEBUG ,所以用 <code> 才能让低级别日志在高级别模式下也显示
spdlog 的 set_level() 为什么有时不生效?
多数情况是调用时机错了——spdlog::set_level() 只影响之后创建的 logger,对已存在的 logger 无效。更隐蔽的问题是:多个 logger 实例各自维护级别,改全局默认级别不会同步到它们。
- 确保在获取 logger 实例前调用
spdlog::set_level(),或者对每个 logger 单独调用logger->set_level(...) - 如果用
spdlog::stdout_logger_mt("name")创建了 logger,它初始级别是spdlog::level::info,不是全局默认值 - 调试技巧:打印当前 logger 级别:
logger->level(),别光信配置文件里写的 - Windows 下注意控制台编码,
ERROR日志含中文时可能乱码,和级别无关,但容易误判为“没输出”
自己写宏实现编译期过滤 DEBUG 日志
想彻底去掉 DEBUG 日志的运行时开销,就得靠预处理宏。但宏展开后容易出问题:参数求值、分号缺失、作用域污染。
立即学习“C++免费学习笔记(深入)”;
- 基础写法:
#define LOG_DEBUG(...) do { if (kDebugEnabled) log_impl(LogLevel::DEBUG, __VA_ARGS__); } while(0) - 必须用
do {...} while(0)包裹,否则if (x) LOG_DEBUG("a"); else ...会语法错误 - 更安全的做法是加编译开关:
#ifdef DEBUG_BUILD,而不是运行时变量,这样未定义时整行被预处理器剔除 - 注意
__FILE__和__LINE__在宏里要写成#__FILE__才能转成字符串字面量
多线程下 std::cout 直接打日志会丢内容
不是级别问题,是竞态——多个线程同时调用 std::cout ,输出会交错甚至截断。哪怕你用了锁,<code>std::endl 的 flush 操作也可能被其他线程打断。
- 最简单解法:用
spdlog或glog这类线程安全 logger,它们内部做了缓冲+原子写入 - 若坚持用
std::cout,至少把整条日志拼成一个字符串再输出:std::cout - 别用
std::endl,改用'\n',避免频繁 flush 带来的性能抖动 - 注意:
std::cerr虽然默认不缓冲,但多线程写它照样乱,不是“线程安全”的代名词
日志分级真正的复杂点不在怎么标级别,而在于“谁来决定当前该用哪个级别”——是配置文件热加载?环境变量?启动参数?这些决策点一旦分散在不同模块,级别就很容易失控。比实现本身更值得花时间理清楚。










