webserver--多缓冲区实现日志系统

程序员行者孙 2024-07-15 12:03:06 阅读 87

😎 作者介绍:我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号:AI Sun,视频号:AI-行者Sun

🎈 本文专栏:本文收录于《项目推荐》系列专栏,相信一份耕耘一份收获,我会继续分享更多优质项目,届时可以拳打字节,脚踢腾讯

🤓 欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。

🖥 随时欢迎您跟我沟通,一起交流,一起成长、进步!

⽇志系统

服务器的⽇志系统是⼀个多⽣产者,单消费者的任务场景:多⽣产者负责把⽇志写⼊缓冲区,单消费者负责把缓冲 区中数据写⼊⽂件。如果只⽤⼀个缓冲区,不光要同步各个⽣产者,

还要同步⽣产者和消费者。⽽且最重要的是需要保证⽣产者与消费者的并发,也就是前端不断写⽇志到缓冲区的同时,后端可以把缓冲区写⼊⽂件。

 所以我们这里采用双缓冲区,这样就可以高效的避免因为读写原因导致的阻塞。

双缓冲技术的基本思路:准备两块

buffer

A

B,

前端往

A

写数据,后端从

B

⾥⾯往硬盘写数据,当

A

写满 后,交换 A

B

,如此反复。使⽤两个

buffer

的好处是在新建⽇志消息的时候不必等待磁盘⽂件操作,也避 免每条新⽇志消息都触发后端⽇志线程。换句话说,前端不是将⼀条条⽇志消息分别送给后端,⽽是将多条⽇ 志消息拼接成⼀个⼤的 buffer

传送给后端,相当于批处理,减少了线程唤醒的开销。

先看看大纲理解一下每个函数的作用。

各个函数的作用:

<code>Log::Log() - 构造函数

初始化Log类的实例。设置日志级别、异步写入标志、写入线程、队列、今日日期等成员变量的初始状态。

Log::~Log() - 析构函数

清理Log类的实例。如果存在异步写入线程,则等待线程结束,关闭队列,加入主线程,并关闭文件指针。如果文件指针存在,则同步写入并关闭日志文件。

Log::GetLevel() - 获取当前日志级别

通过互斥锁保护的成员变量level_,返回当前设置的日志级别。

Log::SetLevel(int level) - 设置日志级别

通过互斥锁保护的成员变量level_,设置新的日志级别。

Log::init(int level, const char* path, const char* suffix, int maxQueueSize) - 初始化日志系统

设置日志级别、日志文件路径、文件后缀和最大队列大小。根据队列大小决定是否开启异步写入模式,并初始化相关的成员变量。创建日志文件并打开用于写入。

Log::write(int level, const char *format, ...) - 写入日志

根据指定的日志级别和格式化字符串写入日志。首先获取当前时间,然后根据日期和行数决定是否需要创建新文件。格式化日志内容并写入缓冲区,如果开启异步写入,则将日志内容推送到队列中,否则直接写入文件。

Log::AppendLogLevelTitle_(int level) - 添加日志级别标题

根据传入的日志级别,向缓冲区追加相应的日志级别标题。

Log::flush() - 刷新日志缓冲区

如果开启异步写入,则调用队列的flush()方法,否则调用fflush()刷新文件指针。

Log::AsyncWrite_() - 异步写入日志

从队列中取出日志内容并写入文件。这是异步线程执行的函数,用于将日志缓冲区的内容异步写入磁盘。

Log::Instance() - 获取日志实例

静态函数,返回Log类的一个实例。这里使用了单例模式,确保整个程序中只存在一个日志实例。

Log::FlushLogThread() - 异步日志写入线程函数

这是异步线程的入口点,它会调用AsyncWrite_()函数来处理异步日志写入任务

具体详细注释代码(太不容易了,点个赞呗,可以关注一下公众号和视频号)

#include "log.h"

using namespace std;

// Log类的构造函数

Log::Log() {

lineCount_ = 0; // 初始化日志行数为0

isAsync_ = false; // 初始化异步写入标志为false

writeThread_ = nullptr; // 初始化写入线程指针为nullptr

deque_ = nullptr; // 初始化日志队列指针为nullptr

toDay_ = 0; // 初始化今日日期标志为0

fp_ = nullptr; // 初始化文件指针为nullptr

}

// Log类的析构函数

Log::~Log() {

// 如果存在写入线程并且可以加入,则等待线程结束

if(writeThread_ && writeThread_->joinable()) {

// 循环直到队列清空

while(!deque_->empty()) {

deque_->flush();

};

// 关闭队列

deque_->Close();

// 等待写入线程结束

writeThread_->join();

}

// 如果文件指针存在

if(fp_) {

// 锁定互斥锁

lock_guard<mutex> locker(mtx_);

// 刷新缓冲区并关闭文件

flush();

fclose(fp_);

}

}

// 获取当前日志级别

int Log::GetLevel() {

// 锁定互斥锁

lock_guard<mutex> locker(mtx_);

// 返回日志级别

return level_;

}

// 设置日志级别

void Log::SetLevel(int level) {

// 锁定互斥锁

lock_guard<mutex> locker(mtx_);

// 设置新的日志级别

level_ = level;

}

// 初始化日志系统

void Log::init(int level, const char* path, const char* suffix,

int maxQueueSize) {

// 设置日志级别

isOpen_ = true;

level_ = level;

// 如果队列大小大于0,则开启异步写入

if(maxQueueSize > 0) {

isAsync_ = true;

// 如果队列不存在,则创建新队列

if(!deque_) {

unique_ptr<BlockDeque<string>> newDeque(new BlockDeque<string>);

deque_ = move(newDeque);

// 创建新的写入线程

std::unique_ptr<std::thread> NewThread(new thread(FlushLogThread));

writeThread_ = move(NewThread);

}

} else {

// 否则关闭异步写入

isAsync_ = false;

}

// 重置日志行数

lineCount_ = 0;

// 获取当前时间

time_t timer = time(nullptr);

struct tm *sysTime = localtime(&timer);

struct tm t = *sysTime;

// 设置日志文件路径和后缀

path_ = path;

suffix_ = suffix;

// 构造日志文件名

char fileName[LOG_NAME_LEN] = {0};

snprintf(fileName, LOG_NAME_LEN - 1, "%s/%04d_%02d_%02d%s",

path_, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, suffix_);

// 设置今日日期

toDay_ = t.tm_mday;

// 锁定互斥锁

lock_guard<mutex> locker(mtx_);

// 清空缓冲区

buff_.RetrieveAll();

// 如果文件指针存在,则关闭旧文件

if(fp_) {

flush();

fclose(fp_);

}

// 打开新文件

fp_ = fopen(fileName, "a");

// 如果文件打开失败,则创建目录后再次尝试

if(fp_ == nullptr) {

mkdir(path_, 0777);

fp_ = fopen(fileName, "a");

}

// 断言文件指针不为空

assert(fp_ != nullptr);

}

// 写入日志

void Log::write(int level, const char *format, ...) {

// 获取当前时间

struct timeval now = {0, 0};

gettimeofday(&now, nullptr);

time_t tSec = now.tv_sec;

struct tm *sysTime = localtime(&tSec);

struct tm t = *sysTime;

va_list vaList;

// 如果日期变化或达到最大日志行数,则创建新文件

if (toDay_ != t.tm_mday || (lineCount_ && (lineCount_ % MAX_LINES == 0))) {

// 锁定互斥锁

unique_lock<mutex> locker(mtx_);

locker.unlock();

// 构造新文件名

char newFile[LOG_NAME_LEN];

char tail[36] = {0};

snprintf(tail, 36, "%04d_%02d_%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);

// 如果日期变化

if (toDay_ != t.tm_mday) {

snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s%s", path_, tail, suffix_);

// 更新今日日期

toDay_ = t.tm_mday;

// 重置日志行数

lineCount_ = 0;

} else {

// 否则更新文件名,包含行数信息

snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s-%d%s", path_, tail, (lineCount_ / MAX_LINES), suffix_);

}

// 重新加锁

locker.lock();

// 刷新缓冲区

flush();

// 关闭旧文件

fclose(fp_);

// 打开新文件

fp_ = fopen(newFile, "a");

// 断言文件指针不为空

assert(fp_ != nullptr);

}

// 锁定互斥锁

unique_lock<mutex> locker(mtx_);

// 增加日志行数

lineCount_++;

// 格式化日志时间

int n = snprintf(buff_.BeginWrite(), 128, "%d-%02d-%02d %02d:%02d:%02d.%06ld ",

t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,

t.tm_hour, t.tm_min, t.tm_sec, now.tv_usec);

// 写入时间到缓冲区

buff_.HasWritten(n);

// 添加日志级别标题

AppendLogLevelTitle_(level);

// 格式化日志内容

va_start(vaList, format);

int m = vsnprintf(buff_.BeginWrite(), buff_.WritableBytes(), format, vaList);

va_end(vaList);

// 写入日志内容到缓冲区

buff_.HasWritten(m);

// 添加换行符

buff_.Append("\n\0", 2);

// 如果开启异步写入且队列未满,则将日志内容推送到队列

if(isAsync_ && deque_ && !deque_->full()) {

deque_->push_back(buff_.RetrieveAllToStr());

} else {

// 否则直接写入文件

fputs(buff_.Peek(), fp_);

}

// 清空缓冲区

buff_.RetrieveAll();

}

// 添加日志级别标题

void Log::AppendLogLevelTitle_(int level) {

// 根据日志级别添加相应的标题

switch(level) {

case 0:

buff_.Append("[debug]: ", 9);

break;

case 1:

buff_.Append("[info] : ", 9);

break;

case 2:

buff_.Append("[warn] : ", 9);

break;

case 3:

buff_.Append("[error]: ", 9);

break;

default:

buff_.Append("[info] : ", 9);

break;

}

}

// 刷新缓冲区,将内容写入文件

void Log::flush() {

// 如果开启异步写入,则调用队列的flush方法

if(isAsync_) {

deque_->flush();

}

// 刷新文件指针

fflush(fp_);

}

// 异步写入日志

void Log::AsyncWrite_() {

// 从队列中取出日志内容并写入文件

string str = "";

while(deque_->pop(str)) {

// 锁定互斥锁

lock_guard<mutex> locker(mtx_);

// 将日志内容写入文件

fputs(str.c_str(), fp_);

}

}

// 获取日志实例

Log* Log::Instance() {

// 创建并返回Log类的单例实例

static Log inst;

return &inst;

}

// 异步日志写入线程函数

void Log::FlushLogThread() {

// 调用实例的异步写入函数

Log::Instance()->AsyncWrite_();

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。