应使用 enum class 定义日志级别以避免隐式转换:enum class LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 };使用时需 static_cast 显式转换,确保类型安全,还可重载 operator 支持流输出。

日志级别怎么定义才不踩 enum 隐式转换的坑
直接用 int 或裸 enum 定义级别,容易在比较或传参时被编译器悄悄转成整数,导致逻辑错乱(比如 if (level == 3) 看似合理,实则绕过类型检查)。推荐用 enum class 强制作用域和类型安全:
enum class LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3
};
使用时必须显式转换:static_cast<int>(level)</int>,虽然多写几个字,但能避免低级误判。如果要支持流输出,可重载 operator,但别为了省事把 <code>enum class 改回裸 enum。
如何让 std::cout 输出带时间戳和级别前缀
每次手动拼接 [2024-06-12 14:23:05][INFO] 太啰嗦,也难统一格式。建议封装一个轻量输出函数,用 std::chrono 获取毫秒级时间,再组合字符串:
void log(LogLevel level, const std::string& msg) {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) % 1000;
std::string level_str;
switch(level) {
case LogLevel::DEBUG: level_str = "DEBUG"; break;
case LogLevel::INFO: level_str = "INFO"; break;
case LogLevel::WARN: level_str = "WARN"; break;
case LogLevel::ERROR: level_str = "ERROR"; break;
}
std::cout << "[" << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S")
<< "." << std::setfill('0') << std::setw(3) << ms.count()
<< "][" << level_str << "] " << msg << "\n";
}
注意两点:一是 std::put_time 要配合 std::localtime(线程不安全,单线程够用;多线程需用 std::gmtime_r + 自己格式化);二是 std::setw 和 std::setfill 必须在同一次输出中生效,否则毫秒可能显示为 5 而非 005。
立即学习“C++免费学习笔记(深入)”;
怎样控制哪些级别实际输出(比如只打 INFO 及以上)
不能靠 if 套一堆 if (level >= LogLevel::INFO) 分散在各处——维护成本高且易漏。应在日志入口统一过滤:
- 定义全局变量
LogLevel g_log_level = LogLevel::INFO,启动时从环境变量或配置读取 - log 函数开头加判断:
if (static_cast<int>(level) (g_log_level)) return;</int> - 避免用
switch或map做级别映射——纯整数比较最快,无额外开销
如果需要运行时动态调高/调低级别(比如调试时临时开 DEBUG),就别把 g_log_level 声明为 const,也别用宏(#define LOG_LEVEL INFO)硬编码,否则改完得重编译。
为什么别急着引入 spdlog 或 glog
简单命令行工具、教学示例、嵌入式小项目里,第三方日志库反而增加依赖、编译时间、二进制体积,还可能引入你不关心的特性(如异步队列、文件轮转、网络发送)。手写上面几十行代码,已能覆盖:分级、时间戳、运行时开关、线程安全(单 cout 本身是线程安全的,但多线程混打会乱序,真需要顺序得加锁)。
真正该换库的信号只有一个:你开始反复修改自己的 log 函数,或者发现 std::cout 在重定向到文件后性能明显下降(此时再考虑缓冲、异步、文件分片等专业能力)。


















