C++ 日志、转存

功能归纳总结如下:
1) 日志文件提供接口让用户配置日志文件名、日志大小上限、历史日志文件数目上限;
2) 日志文件提供Append()接口,让用户向文件追加日志消息;
3) 日志文件在执行Append()接口过程中,自动检测当前日志文件大小:如果追加当前消息后,文件大小超过约定上限,则记录当前消息前,将已有消息转储到历史文件并保证历史日志文件数不超过约定上限;否则,直接记录当前消息;
4) 日志文件提供接口让用户配置是否对历史日志文件进行压缩;

#ifndef _LOGFILE_H
#define _LOGFILE_H

#include <fstream>
#include <iostream>

class LogFile {
public:
    LogFile(const LogFile &) = delete;
    LogFile& operator=(const LogFile &) = delete;
    LogFile(const std::string&, double, unsigned int, bool);
    ~LogFile();
    void Append(std::string &&msg);
    void Rotate();
    double GetFileSize();
    std::string NextHistoryFile();
private:
    std::ofstream ofs_;  // c++ std::ofstream类型对象,通过其操作符"<<"把消息写入日志文件
    std::string file_name_; // 日志文件名
    double cur_size_; // 实时记录当前日志文件大小,避免每次执行Append()操作时调用系统函数获取文件大小;
    double max_size_; // 用户指定的日志文件上限
    unsigned int max_file_num_; // 用户指定的最大历史文件数
    bool compress_; // 是否需要压缩历史文件
};
#endif //_LOGFILE_H

LogFile成员函数说明

LogFile(const std::string&, double, unsigned int): LogFile类的唯一构造函数,可以指定日志文件名,文件大小上限,历史文件数量上限

LogFile::LogFile(const std::string& file_name,
                 double max_size,
                 unsigned int max_file_num,
                 bool compress)
    : file_name_(file_name), max_size_(max_size), max_file_num_(max_file_num),
      compress_(compress)
{
    assert(max_file_num_ > 0);
    cur_size_ = GetFileSize();
    ofs_.open(file_name_, std::ofstream::out|std::ofstream::app);
}

~LogFile(): 析构函数,主要功能是关闭在构造函数中打开的ofstream对象

LogFile::~LogFile()
{
    if (ofs_.is_open()) {
        ofs_.close();
    }
}

void Append(std::string &&msg):LogFile类最重要的接口,让用户向日志文件追加新的消息。其实现逻辑为:在写文件前,先检查当前日志文件大小加上当前消息的长度之和是否会超过创建LogFile对象时指定的日志文件大小上限:如果超过,就将当前文件内容转存到历史文件,并清空当前日志文件(通过Rotate函数),然后,继续把当前消息写入日志文件。 有一点值得说明:获取当前日志文件大小不是通过调用系统函数,而是通过类成员变量cur_size_(构造函数调用一次系统函数为cur_size_赋初值,之后,每次执行Append(), cur_size_都累加消息长度,从而实时追踪日志文件长度), 这样避免了每次执行Append()时都调用系统函数检查文件长度,从而提高了效率

void LogFile::Append(std::string&& msg)
{
    double msg_size = (double)msg.size();
    if (cur_size_+ msg_size >= max_size_) {
        Rotate();
    }

    ofs_ << std::forward<std::string>(msg) << std::endl;
    cur_size_ += msg_size;
}

void Rotate(): 转储函数,当前日志文件内容被转存到某个历史日志文件,当前日志文件被清空并被重新打开

//
// Compress file "old_file" and save data into file "new_file", the
// compression is based on zlib.
//
void LogFile::compress_file(const char *old_file, const char *new_file)
{
    gzFile gf = gzopen(new_file, "wb");
    if (gf == NULL) {
        std::cout << "gzopen() failed" << std::endl;
        return;
    }

    const int BUF_LEN = 500;
    char buf[BUF_LEN];
    size_t reads = 0;

    FILE *fd = fopen(old_file, "rb");
    if (fd == NULL) {
        std::cout << "fopen(" << old_file << ") failed" << std::endl;
    }

    while((reads = fread(buf, 1, BUF_LEN, fd)) > 0) {
        if (ferror(fd)) {
            std::cout << "fread() failed!" << std::endl;
            break;
        }
        gzwrite(gf, buf, reads);
    }

    fclose(fd);
    gzclose(gf);
}

//
// Save existing log messages into history file and empty log file.
//
// Note: call this function only when log file reaches maximum size.
//
void LogFile::Rotate()
{
    if (ofs_.is_open()) {
        ofs_.close();
    }

    std::string history_file = NextHistoryFile();
    if (compress_) {
        compress_file(file_name_.c_str(), history_file.c_str());
    } else {
        std::rename(file_name_.c_str(), history_file.c_str()); // rename用在此处,会出现没有权限修改文件名,解决方法可以是复制一份文件,而不用使用rename
    }
    ofs_.open(file_name_, std::ofstream::out|std::ofstream::trunc);
    cur_size_ = 0;
}

double LogFile::GetFileSize(): 返回当前日志文件大小(bytes),当前实现只针对Linux

double LogFile::GetFileSize()
{
    struct stat statbuf;
    if (stat(file_name_.c_str(), &statbuf) == 0) {
        return (double)statbuf.st_size;
    } else {
        perror("Faild to get log file size");
        return 0;
    }
}

测试

#include "logfile.h"
int main(void)
{
    LogFile lf("testlogfile.log", 1024, 8, false);
    for (auto i = 0; i < 200; i++) {
        lf.Append(std::to_string(i) + ": this is a very very very very very very very very very very very very very very vvery very very very very ery long log");
    }
}

复制文件可以用下面的接口

/*****************************************************************************************
Function:       CopyFile
Description:    复制文件
Input:          SourceFile:原文件路径 NewFile:复制后的文件路径
Return:         1:成功 0:失败
******************************************************************************************/
int CopyFile(char *SourceFile, char *NewFile)
{
	std::ifstream in;
	std::ofstream out;
 
	try
	{
		in.open(SourceFile, std::ios::binary);//打开源文件
		if (in.fail())//打开源文件失败
		{
			std::cout << "Error 1: Fail to open the source file." << std::endl;
			in.close();
			out.close();
			return 0;
		}
		out.open(NewFile, std::ios::binary);//创建目标文件 
		if (out.fail())//创建文件失败
		{
			std::cout << "Error 2: Fail to create the new file." << std::endl;
			out.close();
			in.close();
			return 0;
		}
		else//复制文件
		{
			out << in.rdbuf();
			out.close();
			in.close();
			return 1;
		}
	}
	catch (std::exception e)
	{
	}
}

转载自:https://www.cnblogs.com/wangwenzhi2019/p/11100331.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值