一个轻量高效的C++日志库需支持多级别、线程安全及多输出目标。1. 定义DEBUG/INFO/WARN/ERROR/FATAL五种级别;2. 设计单例Logger类,封装格式化输出与文件/控制台双写入;3. 使用__VA_ARGS__宏自动传入文件名行号,简化调用;4. log函数中通过mutex加锁,按级别过滤并格式化消息写入多目标,确保线程安全。

在C++项目中,一个轻量且高效的日志库能极大提升开发效率和调试能力。实现一个简单的日志系统并不复杂,关键在于设计清晰的接口、支持多级别输出、灵活的输出目标(控制台、文件等),以及线程安全的基础保障。
定义日志级别
日志级别用于区分消息的重要程度,常见的有:
- DEBUG:调试信息,开发阶段使用
- INFO:普通运行信息
- WARN:警告,可能存在问题
- ERROR:错误,程序无法正常执行某功能
- FATAL:严重错误,可能导致程序终止
可以用枚举来表示:
enum class LogLevel {
DEBUG,
INFO,
WARN,
ERROR,
FATAL
};
设计日志记录器类
核心是封装一个单例的Logger类,提供简洁的调用接口。它应支持格式化输出,并允许同时输出到控制台和文件。
立即学习“C++免费学习笔记(深入)”;
基本结构如下:
class Logger {
public:
static Logger& instance() {
static Logger logger;
return logger;
}
void set_level(LogLevel level) { log_level_ = level; }
void set_file_output(const std::string& filename);
void log(LogLevel level, const char* file, int line, const char* format, ...);private:
LogLevel loglevel = LogLevel::DEBUG;
std::FILE* filehandle = nullptr;
std::mutex mutex_; // 保证线程安全
};
通过静态instance方法获取唯一实例,避免全局变量污染。log函数使用可变参数处理格式化字符串,类似printf。
封装宏简化调用
直接调用log函数需要手动传入文件名和行号,使用宏可以自动完成:
#define LOG_DEBUG(fmt, ...) \
Logger::instance().log(LogLevel::DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) \
Logger::instance().log(LogLevel::INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) \
Logger::instance().log(LogLevel::WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) \
Logger::instance().log(LogLevel::ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define LOG_FATAL(fmt, ...) \
Logger::instance().log(LogLevel::FATAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
这样在代码中就可以像这样使用:
LOG_INFO("User %s logged in.", username);
LOG_ERROR("Failed to open file: %s", filename);
实现日志输出逻辑
在log函数中,先判断当前级别是否满足输出条件,再格式化消息并加锁写入:
void Logger::log(LogLevel level, const char* file, int line, const char* format, ...) {
if (level < log_level_) return;
char time_buf[64];
auto now = std::time(nullptr);
std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now));
char msg_buf[1024];
va_list args;
va_start(args, format);
vsnprintf(msg_buf, sizeof(msg_buf), format, args);
va_end(args);
std::lock_guardzuojiankuohaophpcnstd::mutexyoujiankuohaophpcn lock(mutex_);
// 输出到控制台
const char* 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;
case LogLevel::FATAL: level_str = "FATAL"; break;
}
printf("[%s] %s:%d %s\n", time_buf, file, line, msg_buf);
// 同时输出到文件(如果开启)
if (file_handle_) {
fprintf(file_handle_, "[%s] %s %s:%d %s\n", time_buf, level_str, file, line, msg_buf);
fflush(file_handle_);
}}
set_file_output函数用于打开日志文件:
void Logger::set_file_output(const std::string& filename) {
if (file_handle_) {
std::fclose(file_handle_);
}
file_handle_ = std::fopen(filename.c_str(), "a");
}
基本上就这些。这个简易日志库已具备实用功能:分级输出、文件位置追踪、时间戳、线程安全、文件持久化。后续可扩展异步写入、滚动日志、颜色输出等功能,但对大多数小项目来说,这个版本已经够用。











