1. 日志轮转功能的核心价值与实现思路
日志轮转(Log Rotation)是每个C++开发者迟早要面对的基础设施问题。当我在处理一个长期运行的服务端程序时,曾经因为没做日志切割吃过亏——单个日志文件膨胀到32GB,不仅vi打不开,连grep都要等几分钟。这就是为什么我们需要在日志文件达到特定大小时自动切分,形成类似app.log、app.log.1、app.log.2这样的序列。
实现日志轮转的核心机制其实不复杂,主要包含三个关键点:
- 实时监控当前日志文件大小(通过ftell或stat)
- 达到阈值时关闭当前文件并重命名现有文件序列
- 创建新的空白日志文件继续写入
但魔鬼藏在细节里。比如在多线程环境下,如何保证切分瞬间的日志不丢失?如何处理正在被其他进程读取的日志文件?这些才是真正考验实现质量的地方。
2. 基础实现方案与代码结构
2.1 日志类的基本框架
我们先搭建一个最小可用的LogRotator类:
cpp复制class LogRotator {
public:
LogRotator(const std::string& basePath, size_t maxSize);
void write(const std::string& message);
private:
void rotateIfNeeded();
void rotateFiles();
std::string basePath_;
size_t maxSize_;
FILE* currentFile_;
};
关键参数说明:
- basePath_:日志文件基础路径(如"/var/log/myapp"会生成myapp.log、myapp.log.1等)
- maxSize_:单个文件最大字节数(建议10-100MB,根据业务需求调整)
- currentFile_:当前写入的文件指针
2.2 文件大小检测实现
每次写入前检查大小的经典做法:
cpp复制void LogRotator::rotateIfNeeded() {
if (!currentFile_) return;
long pos = ftell(currentFile_);
if (pos < 0 || static_cast<size_t>(pos) < maxSize_) return;
rotateFiles();
}
注意:ftell在Windows下返回的是long类型,在Linux下是off_t。跨平台项目需要特别注意类型转换问题。
3. 文件轮转的核心算法
3.1 文件重命名策略
最关键的旋转逻辑如下:
cpp复制void LogRotator::rotateFiles() {
fclose(currentFile_);
// 重命名现有文件序列(app.log.2 -> app.log.3等)
for (int i = maxBackups_ - 1; i > 0; --i) {
std::string oldName = basePath_ + "." + std::to_string(i);
std::string newName = basePath_ + "." + std::to_s
