日志系统采用了单例模式,单例模式保证一个类只有一个实例,同时提供了一个可供全局访问该实例的静态方法,该实例可以被程序的所有模块共享。
实现方法:通过一个类静态成员返回局部静态实例,可供全局访问。同时私有化类的构造函数、析构函数。防止外界创建单例类的对象。
class Log { public: static Log* get_instance() { static Log instance; return &instance; } private: Log(); ~Log(); };主要包括判断同步日志/异步日志、初始化日志类的成员变量(日志的缓冲区申请内存、日志最大行数、日志的日期等)、创建日志文件(日志文件名是根据日期命名的) 如果是异步方式写入,在初始化时开辟一条日志线程,在该线程中向日志文件写入信息。
目前定义有4种类型,debug,info,warn,errno。
日志系统做到按天分类,或者单个日志文件超过最大行数也需要新建日志文件。 所以 1、当前日期与当前日志不在同一天时需要新建日志文件, 2、或者当天日志写入的行数是单个日志文件最大行数的倍数时,需要新建日志文件
日志信息按照规定的格式写入日志文件 年-月-日 时-分-秒.微秒 日志类型 真正的信息
2020-06-29 18:01:42.399499 [info]: send data to the client(192.168.43.1)int fflush(FILE *stream)
每次向日志文件写入信息时,都可以调用该函数,及时的刷新读写缓冲区。
实际上调用printf()等时都是先将数据写到缓冲区当中,当遇到\n等换行符号时才会刷新缓冲区写入到具体的流stream中。如果上一次写入的信息还在缓冲区中,下一次又写入到缓冲区的数据可能会覆盖上一次的数据,造成错误。fflush()方法可以及时的刷新缓冲区。
例子:
#include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() { int i = 0; for(i = 0;i < 10;i ++) { printf("%d",i); sleep(1); } return 0; }0123456789会一次性打印出来,而不是一个个的打印,说明确实是先输出到缓冲区当中了。
该异步日志系统使用了经典的生产者消费者并发模型,生产者消费者共享了一个队列。 服务器的主线程和各个工作线程向该队列写入具体的信息,是为生产者; 单独开辟的一条线程会从队列中取出信息,写入到日志文件中。是为消费者。
异步写入时一定要在线程函数中每次写入之后调用fflush(FILE* stream)刷新缓冲区,否则会出现日志信息不能及时写入文件的情况。
void* Log::async_write_log() { string log_str; //从阻塞队列中取出一个日志string,写入文件 while(m_log_queue->pop(log_str)) { m_mutex.lock(); fputs(log_str.c_str(),m_fp); fflush(m_fp);// m_mutex.unlock(); } }日志类中多次用到互斥锁,容易出现在一个线程中对一个已经加锁的互斥锁再次加锁,出现死锁,此时会一直阻塞下去。