在C++项目开发中,日志文件记录是排查问题、监控程序运行状态的重要手段,而给日志添加时间戳、按级别分级写入,能让日志内容更清晰,便于后续分析。实现这个功能需要结合时间处理、文件操作和逻辑分级几个部分,下面逐步讲解具体实现方法。

核心需求拆解
要实现带时间戳的日志分级写入,需要完成三个核心任务:
- 获取当前系统时间并格式化为可读的时间戳字符串
- 定义清晰的日志级别,不同级别对应不同的日志前缀
- 将日志内容按级别和时间戳拼接后写入指定的日志文件,同时处理文件打开、写入异常等问题
时间戳获取实现
C++中可以通过<chrono>和<iomanip>库获取当前系统时间并格式化,下面是获取标准时间戳的示例代码:
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <string>
// 获取当前时间戳,格式为 YYYY-MM-DD HH:MM:SS
std::string get_current_timestamp() {
auto now = std::chrono::system_clock::now();
std::time_t now_time = std::chrono::system_clock::to_time_t(now);
std::tm local_tm;
// 线程安全的时间转换,避免localtime的隐患
localtime_r(&now_time, &local_tm);
std::ostringstream oss;
oss << std::put_time(&local_tm, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
日志级别定义
常见的日志级别可以分为调试、信息、警告、错误、致命错误五类,我们可以用枚举来定义这些级别,同时给每个级别设置对应的前缀文本:
#include <string>
// 日志级别枚举
enum class LogLevel {
DEBUG, // 调试级别,开发阶段使用
INFO, // 信息级别,常规运行信息
WARNING, // 警告级别,潜在问题提示
ERROR, // 错误级别,功能运行出错
FATAL // 致命错误级别,程序即将退出
};
// 日志级别转字符串
std::string log_level_to_string(LogLevel level) {
switch (level) {
case LogLevel::DEBUG:
return "DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
日志写入核心逻辑
日志写入需要打开指定的日志文件,将时间戳、日志级别、日志内容拼接成完整的一行,然后写入文件并刷新缓冲区,避免程序异常退出时日志丢失。下面是完整的日志写入实现:
#include <fstream>
#include <iostream>
#include <string>
// 写入日志到文件
bool write_log_to_file(const std::string& log_file_path, LogLevel level, const std::string& content) {
// 拼接完整日志行:[时间戳] [级别] 日志内容
std::string log_line = "[" + get_current_timestamp() + "] [" + log_level_to_string(level) + "] " + content + "n";
// 以追加模式打开文件
std::ofstream log_file(log_file_path, std::ios::app);
if (!log_file.is_open()) {
std::cerr << "无法打开日志文件: " << log_file_path << std::endl;
return false;
}
log_file << log_line;
// 手动刷新缓冲区,确保日志及时写入文件
log_file.flush();
log_file.close();
return true;
}
完整使用示例
我们可以把上述功能封装成一个简单的日志类,方便在项目中复用,下面是使用示例:
#include <string>
class SimpleLogger {
private:
std::string log_file_path;
public:
SimpleLogger(const std::string& file_path) : log_file_path(file_path) {}
void log(LogLevel level, const std::string& content) {
write_log_to_file(log_file_path, level, content);
}
};
int main() {
SimpleLogger logger("app.log");
logger.log(LogLevel::INFO, "程序启动成功");
logger.log(LogLevel::DEBUG, "当前用户ID: 1001");
logger.log(LogLevel::WARNING, "配置文件缺失可选字段");
logger.log(LogLevel::ERROR, "数据库连接失败");
return 0;
}
注意事项
在实际项目中使用时,还需要注意几个问题:
- 多线程场景下需要给日志写入加互斥锁,避免多个线程同时写入导致日志内容错乱
- 可以设置日志文件大小上限,超过上限后自动创建新的日志文件,避免单个文件过大
- 生产环境可以关闭调试级别的日志输出,减少不必要的IO开销
- 时间戳获取如果追求更高精度,可以结合<chrono>的毫秒、微秒接口,在格式中添加对应精度内容