【日志系统-文件】

文件写对象  AppendFile

高效地将数据追加到写入文件,使用自定义缓冲区减少系统调用次数,提高写入性能。

主要方法:
封装OS提供的创建/打开文件、写文件、关闭文件等操作

相关函数详解

FILE* fopen(const char* filename, const char* mode);
 

参数说明

  • filename:需要打开的文件路径,可以是绝对路径或相对路径。
  • mode:文件打开模式,控制文件的读写权限和操作方式。

返回值

  • 成功时返回指向FILE对象的指针。
  • 失败时返回NULL,并设置全局错误码errno

常见打开模式

  • "r":以只读方式打开文件,文件必须存在。
  • "w":以只写方式打开文件,文件不存在则创建,存在则清空内容。
  • "a":以追加方式打开文件,写入内容自动追加到文件末尾。
  • "r+":以读写方式打开文件,文件必须存在。
  • "w+":以读写方式打开文件,文件不存在则创建,存在则清空内容。
  • "a+":以读写方式打开文件,写入内容自动追加到文件末尾。

二进制模式 在模式字符串中添加'b'(如"rb""wb+"),用于处理二进制文件,避免文本模式下的换行符转换。

void setbuffer(FILE *stream, char *buffer, size_t size);
 

参数说明

  • stream:指向需要设置缓冲区的文件流指针。
  • buffer:指向用于作为缓冲区的字符数组的指针。若为NULL,表示不使用缓冲区。
  • size:指定缓冲区的大小,以字节为单位。

功能说明 setbuffer函数的主要作用是为文件流指定一个自定义的缓冲区,从而控制文件的读写缓冲方式。当对文件进行读写操作时,数据会先在缓冲区中进行处理,而不是直接与文件进行交互。这样可以减少对文件的直接读写次数,提高读写效率。

使用场景

  • 向文件写入数据时,数据会先被写入到缓冲区中。
  • 当缓冲区满或执行fflush()函数时,缓冲区中的数据才会被真正写入到文件中。

成员变量

private:
        static const size_t FILE_BUFF_SIZE = 1024 * 1024*10; // 10m;缓冲区大小
	    std::unique_ptr<char[]> buffer_;                //自定义文件缓冲区
        FILE *fp_;                                     //文件指针
        size_t writeBytes_;                          //已写入文件的总字数
        size_t write(const char *msg, const size_t len); 

成员函数

  • 构造函数
   //构造函数,打开指定文件并初始化为追加模式
        AppendFile(const std::string &filename)
        :buffer_(new char[FILE_BUFF_SIZE]),fp_{nullptr},writenBytes_{0}
        {
	        fp_=fopen(filename.c_str(),"a");  //打开或创建文件
	        assert(fp_!=nullptr);
	        setbuffer(fp_,buffer_.get(),FILE_BUFF_SIZE);
	//当数据写满缓冲区时会自动触发系统调用将数据写入磁盘
	        
        }
    //析构函数,确保文件被正确关闭
        ~AppendFile()
        {
	        fclose(fp_);
	        fp_=nullptr;
	        buffer_.reset();
        }
  • 追加数据到文件
        void append(const char *msg, const size_t len)
        {
	        size_t n=write(msg,len);  //写入的字节数
	        size_t remain=len-n;      //还剩多少没写进去,可能缓冲区满了
	        //文件是可以动态增长的
	        while(remain>0){
		        size_t x=write(msg+n,remain);
		        if(x==0){   //还未写进去,可能文件满了
			        int err=ferror(fp_);
			        if(err){
				        fprintf(stderr,"appendFile::append failed %s\n",strerror(err));
				        break;
			        }
		        }
	        }
	        n+=x;
	        wrtieBytes_+=n;
        }

日志文件对象  LogFile

主要方法:
1. 构建日志文件名称
2. 构建新的回滚文件
3. 日志信息写入文件缓冲区
4. 刷新函数,将文件缓冲区的数据写入文件


此项目回滚只实现了基于文件大小和基于时间(按天)的日志回滚功能,当文件超出所设定的上限时,产生一个新文件;按天回滚,每到00:00就会产生一个新文件。
未实现当文件超过所设定的时间范围就会被删除的功能

成员变量

	private:
		const string basename_;      //日志文件基础名
		const size_t rollSize_;      //日志文件滚动的大小阈值
		const int flushInterval_;    //刷新间隔 s
		
//用于控制检查频率,避免每次写入日志都耗时进行条件检查(大小,时间)
		const int checkEventN_;      //检查滚动条件的事件次数阈值
		int count_;                  //记录日志写入次数,用于触发检查

		time_t startOfPeriod_;       //当前日志周期开始时间(按天算)
		time_t lastRoll_;            //上次滚动文件的时间
		time_t lastFlush;            //上次刷新文件的时间
		static const int kRollPerSeconds_ = 60 * 60 * 24; // 一天的秒数,用于按天滚动

		std::unique_ptr<AppendFile>file_;        //文件写入器
		std::unique_ptr<std::mutex> mutex_;      //互斥锁指针

重要成员函数

  • 构造函数
LogFile(const std::string &basename,
                     size_t rollSize=1024*1024,
                     int flushInterval=3,
                     int checkEventN=30,
                     bool threadSafe=true)
	        : basename_(basename),
	          rollSize_(rollSize),
	          flushInterval_(flushInterval),
	          checkEventN_(checkEventN),
	          mutex_(threadSafe ? std::make_unique<std::mutex>() : nullptr),
	          count_(0),
	          startOfPeriod_(0),
	          lastRoll_(0),
	          lastFlush_(0)
	          {
		          rollFile();
	          }
  • 回滚函数
		bool rollFile()
		{
			tulun::Timestamp t;
			t.now();
			string filename=getLogFileName(basename_,t);
			time_t start=(t.getSecond()/kRollPerSeconds_) *kRollPerSeconds_;

			if(t.getSecond()>lastRoll_)
			{
				lastRoll_=t.getSecond();
				lastFlush_=t.getSecond();
				startOfPeriod_=start;
//reset()释放当前管理的对象,并接管新传入的指针
				file_.reset(new tulun::AppendFile(filename));
				return true;
			
			}
			return false;
		}


	};
  • 默认文件名
std::string LogFile::getLogFileName(const std::string &basename, const tulun::Timestamp &now)
{
	string filename;
	filename.reserve(basename.size()+128);
	filename=basename;
	filename+=".";
	filename+=now.toFormattedFile();
	filenmae+=".";
	filename+=hostname();
	std::stringstream ss;
	ss<<"."<<pid()<<".log";
	filename+=ss.str();
	return filename;
	
}

同步日志分析

当开启线程安全模式(threadSafe为true)当在创建 LogFile对象时将 threadSafe参数设置为 true,LogFile类会创建一个互斥锁 、mutex_。在调用 append方法时,会通过这个互斥锁来保护对 append_unlocked方法的调用,

void LogFile::append(const char *msg, const size_t len)
{
    if(mutex_)
    {
        std::unique_lock<std::mutex> lock(*mutex_);
        append_unlocked(msg, len);
    }
    else
    {
        append_unlocked(msg, len);
    }
}

在 `append_unlocked` 方法中,当满足特定条件时会调用 `file_->flush()` 进行缓冲区刷新,代码如下:

void LogFile::append_unlocked(const char *msg, const size_t len)
{
    file_->append(msg, len);
    if (file_->getWriteBytes()+len > rollSize_)   //预判断
    {
        rollFile();
    }
    else
    {
        count_ += 1;
        if (count_ > checkEventN_)
        {
            count_ = 0;
            time_t now = ::time(nullptr);
            time_t thisPeriod = (now / kRollPerSeconds_) * kRollPerSeconds_;
            if (thisPeriod != startOfPeriod_)
            {
                rollFile();
            }
            else if (now - lastFlush_ > flushInterval_)
            {
                lastFlush_ = now;
                file_->flush();
            }
        }
    }
}

在这种模式下,当一个线程获取互斥锁并执行`append_unlocked`方法时,在执行 
`file_->flush()` 操作(即缓冲区往磁盘写内容)时,其他线程会被阻塞`std::unique_lock<std::mutex> lock(*mutex_);` 这一行,直到持有锁的线程释放锁,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值