likes
comments
collection
share

C++ Linux轻量级WebServer(五)日志系统

作者站长头像
站长
· 阅读数 33

介绍

系统日志是用来记录服务器的运行状态,以保证系统的正常运行,记录的信息如时间日期、客户端的读写操作、当前客户端连接数量、Error与Warn状况等,Tiny Webserver是采用单例模式与阻塞队列实现的异步的日志系统,如下为日志的记录情况:

C++ Linux轻量级WebServer(五)日志系统

实现过程

Log类在被实例化时系统有且最多只有一个Log类的对象即单例模式,它是如何实现的呢?

首先会Log类的构造函数、拷贝构造函数和赋值运算符重载函数私有化,由此外界就不能直接创建Log类对象,这时我们需要一个静态Log类型的对象,以便系统使用同一的类对象,而外界想要获取静态Log类型的对象,就需要定义一个静态成员方法,因为只有静态成员方法才能访问静态成员变量,由此实现Log类的单例模式。

Log类及Instance函数的定义:

class Log {
public:
    void init(int level, const char* path = "./log",
                const char* suffix =".log",
                int maxQueueCapacity = 1024);

    static Log* Instance();    
private:
    Log();
    virtual ~Log();
};
Log* Log::Instance() {  // 懒汉式,不存在线程安全问题
    static Log inst;
    return &inst;
}

系统中有4种类型的日志,分别是LOG_DEBUGLOG_INFOLOG_WARNLOG_ERROR,它们共同使用LOG_BASE以level来区分不同级别的日志,以实现代码的复用。同时自己初始设置的日志等级可以控制不同级别的日志是否被记录

log.h宏定义代码如下:

// level = 1
// LOG_DEBUG  0 <= 1  y
// LOG_INFO   1 <= 1  y
// LOG_WARN   2 <= 1  n
// LOG_ERROR  3 <= 1  n
#define LOG_BASE(level, format, ...) \
    do {\
        Log* log = Log::Instance();\  // 获取Log类对象
        if (log->IsOpen() && log->GetLevel() <= level) {\
            log->write(level, format, ##__VA_ARGS__); \
            log->flush();\
        }\
    } while(0);

#define LOG_DEBUG(format, ...) do {LOG_BASE(0, format, ##__VA_ARGS__)} while(0);
#define LOG_INFO(format, ...)  do {LOG_BASE(1, format, ##__VA_ARGS__)} while(0);
#define LOG_WARN(format, ...)  do {LOG_BASE(2, format, ##__VA_ARGS__)} while(0);
#define LOG_ERROR(format, ...) do {LOG_BASE(3, format, ##__VA_ARGS__)} while(0);

webserver.cpp中,如果开启日志即openLog = true时,则会先调用init()初始化日志信息,当maxQueueSize > 0时则会创建一个阻塞队列与写线程,值得注意的是日志系统是单线程模式,因为并不需要较高的并发量,写线程的回调函数则当阻塞队列不空时持续向FILE*类型的指针fp_写入字符串,再使用fflush(fp_)刷新至文件中。

init函数定义:

void Log::init(int level = 1, const char* path, const char* suffix,
    int maxQueueSize) {
    isOpen_ = true;
    level_ = level;
    if(maxQueueSize > 0) {
        isAsync_ = true;
        if(!deque_) {
            unique_ptr<BlockDeque<std::string>> newDeque(new BlockDeque<std::string>);
            deque_ = move(newDeque);
            
            std::unique_ptr<std::thread> NewThread(new thread(FlushLogThread));
            writeThread_ = move(NewThread);  // 写线程
        }
    } else {
        isAsync_ = false;
    }
}

FlushLogThread回调函数:

void Log::FlushLogThread() {
    Log::Instance()->AsyncWrite_();
}

void Log::AsyncWrite_() {
    string str = "";
    while(deque_->pop(str)) {
        lock_guard<mutex> locker(mtx_);
        fputs(str.c_str(), fp_);
    }
}
void Log::flush() {
    if(isAsync_) { 
        deque_->flush(); 
    }
    fflush(fp_);
}

当使用LOG_DEBUGLOG_INFOLOG_WARNLOG_ERROR时,则会宏替换为LOG_BASELOG_BASE继续宏替换至执行代码,然后依据日志开关与等级是否记录,如果记录则会调用write()函数,在write()函数中会制作日志记录如日志日期、日志行数、日志内容等至buff_中,然后添加至阻塞队列中,在AsyncWrite_()函数的循环能继续向下执行,向FILE*类型的指针fp_写入字符串,然后再调用flush()函数刷新至日志文件中,这是日志记录的整个过程,可结合上述的具体实现。而write()函数可自行查看源代码。

结尾

至此日志系统的讲解结束,这五篇文章是将WebServer是如何实现的解析的较为彻底,但是具体的实现细节还需要大家认真看源代码,相信这五篇文章能够让读者能够更轻松的认识WebServer的实现原理,后续的数据库连接池与单元测试及压力测试会继续补上。

转载自:https://juejin.cn/post/7108553758753161230
评论
请登录