TODO
本文内容 日志系统分为两部分,其一是单例模式与阻塞队列的定义,其二是日志类的定义与使用。
本篇将介绍日志类的定义与使用,具体的涉及到基础API,流程图与日志类定义,功能实现。
基础API ,描述fputs,可变参数宏__VA_ARGS__,fflush
流程图与日志类定义 ,描述日志系统整体运行流程,介绍日志类的具体定义
功能实现 ,结合代码分析同步、异步写文件逻辑,分析超行、按天分文件和日志分级的具体实现
基础API 为了更好的源码阅读体验,这里对一些API用法进行介绍。
fputs 1 2 3 #include <stdio.h> int fputs (const char *str, FILE *stream) ;
str,一个数组,包含了要写入的以空字符终止的字符序列。
stream,指向FILE对象的指针,该FILE对象标识了要被写入字符串的流。
可变参数宏__VA_ARGS__ __VA_ARGS__是一个可变参数的宏,定义时宏定义中参数列表的最后一个参数为省略号,在实际使用时会发现有时会加##,有时又不加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #define my_print1(...) printf(__VA_ARGS__) #define my_print2(format, ...) printf(format, __VA_ARGS__) #define my_print3(format, ...) printf(format, ##__VA_ARGS__) ``` \_\_VA\_ARGS\_\_宏前面加上##的作用在于,当可变参数的个数为0 时,这里printf参数列表中的的##会把前面多余的"," 去掉,否则会编译出错,建议使用后面这种,使得程序更加健壮。 #### **<span style="color: #00ACC1; " >fflush</span>** ```C++ #include <stdio.h> int fflush (FILE *stream);
fflush()会强迫将缓冲区内的数据写回参数stream 指定的文件中,如果参数stream 为NULL,fflush()会将所有打开的文件数据更新。
在使用多个输出函数连续进行多次输出时,有可能下一个数据再上一个数据还没输出完毕,还在输出缓冲区中时,下一个printf就把另一个数据加入输出缓冲区,结果冲掉了原来的数据,出现输出错误。
在prinf()后加上fflush(stdout); 强制马上输出,可以避免出现上述错误。
流程图与日志类定义 流程图
日志文件
局部变量的懒汉模式获取实例
生成日志文件,并判断同步和异步写入方式
同步
判断是否分文件
直接格式化输出内容,将信息写入日志文件
异步
判断是否分文件
格式化输出内容,将内容写入阻塞队列,创建一个写线程,从阻塞队列取出内容写入日志文件
日志类定义 通过局部变量的懒汉单例模式创建日志实例,对其进行初始化生成日志文件后,格式化输出内容,并根据不同的写入方式,完成对应逻辑,写入日志文件。
日志类包括但不限于如下方法,
公有的实例获取方法
初始化日志文件方法
异步日志写入方法,内部调用私有异步方法
内容格式化方法
刷新缓冲区
…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 class Log { public : static Log *get_instance () { static Log instance; return &instance; } bool init (const char *file_name, int log_buf_size = 8192 , int split_lines = 5000000 , int max_queue_size = 0 ) ; static void *flush_log_thread (void *args) { Log::get_instance ()->async_write_log (); } void write_log (int level, const char *format, ...) ; void flush (void ) ; private : Log (); virtual ~Log (); void *async_write_log () { string single_log; while (m_log_queue->pop (single_log)) { m_mutex.lock (); fputs (single_log.c_str (), m_fp); m_mutex.unlock (); } } private : char dir_name[128 ]; char log_name[128 ]; int m_split_lines; int m_log_buf_size; long long m_count; int m_today; FILE *m_fp; char *m_buf; block_queue<string> *m_log_queue; bool m_is_async; locker m_mutex; }; #define LOG_DEBUG(format, ...) Log::get_instance()->write_log(0, format, __VA_ARGS__) #define LOG_INFO(format, ...) Log::get_instance()->write_log(1, format, __VA_ARGS__) #define LOG_WARN(format, ...) Log::get_instance()->write_log(2, format, __VA_ARGS__) #define LOG_ERROR(format, ...) Log::get_instance()->write_log(3, format, __VA_ARGS__) #endif
日志类中的方法都不会被其他程序直接调用,末尾的四个可变参数宏提供了其他程序的调用方法。
前述方法对日志等级进行分类,包括DEBUG,INFO,WARN和ERROR四种级别的日志。
功能实现 init函数实现日志创建、写入方式的判断。
write_log函数完成写入日志文件中的具体内容,主要实现日志分级、分文件、格式化输出内容。
生成日志文件 && 判断写入方式 通过单例模式获取唯一的日志类,调用init方法,初始化生成日志文件,服务器启动按当前时刻创建日志,前缀为时间,后缀为自定义log文件名,并记录创建日志的时间day和行数count。
写入方式通过初始化时是否设置队列大小 (表示在队列中可以放几条数据)来判断,若队列大小为0,则为同步,否则为异步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 bool Log::init (const char *file_name, int log_buf_size, int split_lines, int max_queue_size) { if (max_queue_size >= 1 ) { m_is_async = true ; m_log_queue = new block_queue <string>(max_queue_size); pthread_t tid; pthread_create (&tid, NULL , flush_log_thread, NULL ); } m_log_buf_size = log_buf_size; m_buf = new char [m_log_buf_size]; memset (m_buf, '\0' , sizeof (m_buf)); m_split_lines = split_lines; time_t t = time (NULL ); struct tm *sys_tm = localtime (&t); struct tm my_tm = *sys_tm; const char *p = strrchr (file_name, '/' ); char log_full_name[256 ] = {0 }; if (p == NULL ) { snprintf (log_full_name, 255 , "%d_%02d_%02d_%s" , my_tm.tm_year + 1900 , my_tm.tm_mon + 1 , my_tm.tm_mday, file_name); } else { strcpy (log_name, p + 1 ); strncpy (dir_name, file_name, p - file_name + 1 ); snprintf (log_full_name, 255 , "%s%d_%02d_%02d_%s" , dir_name, my_tm.tm_year + 1900 , my_tm.tm_mon + 1 , my_tm.tm_mday, log_name); } m_today = my_tm.tm_mday; m_fp = fopen (log_full_name, "a" ); if (m_fp == NULL ) { return false ; } return true ; }
日志分级与分文件 日志分级的实现大同小异,一般的会提供五种级别,具体的,
Debug,调试代码时的输出,在系统实际运行时,一般不使用。
Warn,这种警告与调试时终端的warning类似,同样是调试代码时使用。
Info,报告系统当前的状态,当前执行的流程或接收的信息等。
Error和Fatal,输出系统的错误信息。
上述的使用方法仅仅是个人理解,在开发中具体如何选择等级因人而异。项目中给出了除Fatal外的四种分级,实际使用了Debug,Info和Error三种。
超行、按天分文件逻辑,具体的,
日志写入前会判断当前day是否为创建日志的时间,行数是否超过最大行限制
若为创建日志时间,写入日志,否则按当前时间创建新log,更新创建时间和行数
若行数超过最大行限制,在当前日志的末尾加count/max_lines为后缀创建新log
将系统信息格式化后输出,具体为:格式化时间 + 格式化内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 void Log::write_log (int level, const char *format, ...) { struct timeval now = {0 , 0 }; gettimeofday (&now, NULL ); time_t t = now.tv_sec; struct tm *sys_tm = localtime (&t); struct tm my_tm = *sys_tm; char s[16 ] = {0 }; switch (level) { case 0 : strcpy (s, "[debug]:" ); break ; case 1 : strcpy (s, "[info]:" ); break ; case 2 : strcpy (s, "[warn]:" ); break ; case 3 : strcpy (s, "[erro]:" ); break ; default : strcpy (s, "[info]:" ); break ; } m_mutex.lock (); m_count++; if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0 ) { char new_log[256 ] = {0 }; fflush (m_fp); fclose (m_fp); char tail[16 ] = {0 }; snprintf (tail, 16 , "%d_%02d_%02d_" , my_tm.tm_year + 1900 , my_tm.tm_mon + 1 , my_tm.tm_mday); if (m_today != my_tm.tm_mday) { snprintf (new_log, 255 , "%s%s%s" , dir_name, tail, log_name); m_today = my_tm.tm_mday; m_count = 0 ; } else { snprintf (new_log, 255 , "%s%s%s.%lld" , dir_name, tail, log_name, m_count / m_split_lines); } m_fp = fopen (new_log, "a" ); } m_mutex.unlock (); va_list valst; va_start (valst, format); string log_str; m_mutex.lock (); int n = snprintf (m_buf, 48 , "%d-%02d-%02d %02d:%02d:%02d.%06ld %s " , my_tm.tm_year + 1900 , my_tm.tm_mon + 1 , my_tm.tm_mday, my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s); int m = vsnprintf (m_buf + n, m_log_buf_size - 1 , format, valst); m_buf[n + m] = '\n' ; m_buf[n + m + 1 ] = '\0' ; log_str = m_buf; m_mutex.unlock (); if (m_is_async && !m_log_queue->full ()) { m_log_queue->push (log_str); } else { m_mutex.lock (); fputs (log_str.c_str (), m_fp); m_mutex.unlock (); } va_end (valst); }