项目中日志文件实现2(日志类的定义与使用)

原文链接:
https://mp.weixin.qq.com/s/f-ujwFyCe1LZa3EB561ehA

1.fputs
#include <stdio.h>
int fputs(const char *str, FILE *stream);

str,一个数组,包含了要写入的以空字符终止的字符序列。
stream,指向FILE对象的指针,该FILE对象标识了要被写入字符串的流。

2.fflush
#include <stdio.h>
int fflush(FILE *stream);

fflush()会强迫将缓冲区内的数据写回参数stream 指定的文件中,如果参数stream 为NULL,fflush()会将所有打开的文件数据更新。

在使用多个输出函数连续进行多次输出到控制台时,有可能下一个数据再上一个数据还没输出完毕,还在输出缓冲区中时,下一个printf就把另一个数据加入输出缓冲区,结果冲掉了原来的数据,出现输出错误。

在prinf()后加上fflush(stdout); 强制马上输出到控制台,可以避免出现上述错误。

3.流程图
(1)日志文件
局部变量的懒汉模式获取实例
生成日志文件,并判断同步和异步写入方式

(2)同步
判断是否分文件
直接格式化输出内容,将信息写入日志文件

(3)异步
判断是否分文件
格式化输出内容,将内容写入阻塞队列,创建一个写线程,从阻塞队列取出内容写入日志文件
在这里插入图片描述
4.日志类定义

通过局部变量的懒汉单例模式创建日志实例,对其进行初始化生成日志文件后,格式化输出内容,并根据不同的写入方式,完成对应逻辑,写入日志文件。
日志类包括但不限于如下方法:
公有的实例获取方法
初始化日志文件方法
异步日志写入方法,内部调用私有异步方法
内容格式化方法
刷新缓冲区

5.功能实现
init函数实现日志创建、写入方式的判断。

write_log函数完成写入日志文件中的具体内容,主要实现日志分级、分文件、格式化输出内容。

生成日志文件 && 判断写入方式
通过单例模式获取唯一的日志类,调用init方法,初始化生成日志文件,服务器启动按当前时刻创建日志,前缀为时间,后缀为自定义log文件名,并记录创建日志的时间day和行数count。

写入方式通过初始化时是否设置队列大小(表示在队列中可以放几条数据)来判断,若队列大小为0,则为同步,否则为异步。

 1//异步需要设置阻塞队列的长度,同步不需要设置
 2bool Log::init(const char *file_name, int log_buf_size, int split_lines, int max_queue_size)
 3{
 4    //如果设置了max_queue_size,则设置为异步
 5    if (max_queue_size >= 1)
 6    {
 7        //设置写入方式flag
 8        m_is_async = true;
 9
10        //创建并设置阻塞队列长度
11        m_log_queue = new block_queue<string>(max_queue_size);
12        pthread_t tid;
13
14        //flush_log_thread为回调函数,这里表示创建线程异步写日志
15        pthread_create(&tid, NULL, flush_log_thread, NULL);
16    }
17
18    //输出内容的长度
19    m_log_buf_size = log_buf_size;
20    m_buf = new char[m_log_buf_size];
21    memset(m_buf, '\0', sizeof(m_buf));
22
23    //日志的最大行数
24    m_split_lines = split_lines;
25
26    time_t t = time(NULL);
27    struct tm *sys_tm = localtime(&t);
28    struct tm my_tm = *sys_tm;
29
30    //从后往前找到第一个/的位置
31    const char *p = strrchr(file_name, '/');
32    char log_full_name[256] = {0};
33
34    //相当于自定义日志名
35    //若输入的文件名没有/,则直接将时间+文件名作为日志名
36    if (p == NULL)
37    {
38        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);
39    }
40    else
41    {
42        //将/的位置向后移动一个位置,然后复制到logname中
43        //p - file_name + 1是文件所在路径文件夹的长度
44        //dirname相当于./
45        strcpy(log_name, p + 1);
46        strncpy(dir_name, file_name, p - file_name + 1);
47
48        //后面的参数跟format有关
49        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);
50    }
51
52    m_today = my_tm.tm_mday;
53
54    m_fp = fopen(log_full_name, "a");
55    if (m_fp == NULL)
56    {
57        return false;
58    }
59
60    return true;
61}

日志分级与分文件
日志分级的实现大同小异,一般的会提供五种级别,具体的,

Debug,调试代码时的输出,在系统实际运行时,一般不使用。

Warn,这种警告与调试时终端的warning类似,同样是调试代码时使用。

Info,报告系统当前的状态,当前执行的流程或接收的信息等。

Error和Fatal,输出系统的错误信息。

上述的使用方法仅仅是个人理解,在开发中具体如何选择等级因人而异。项目中给出了除Fatal外的四种分级,实际使用了Debug,Info和Error三种。

超行、按天分文件逻辑,具体的,

日志写入前会判断当前day是否为创建日志的时间,行数是否超过最大行限制

若为创建日志时间,写入日志,否则按当前时间创建新log,更新创建时间和行数

若行数超过最大行限制,在当前日志的末尾加count/max_lines为后缀创建新log

将系统信息格式化后输出,具体为:格式化时间 + 格式化内容

void Log::write_log(int level, const char *format, ...)
  2{
  3    struct timeval now = {0, 0};
  4    gettimeofday(&now, NULL);
  5    time_t t = now.tv_sec;
  6    struct tm *sys_tm = localtime(&t);
  7    struct tm my_tm = *sys_tm;
  8    char s[16] = {0};
  9
 10    //日志分级
 11    switch (level)
 12    {
 13    case 0:
 14        strcpy(s, "[debug]:");
 15        break;
 16    case 1:
 17        strcpy(s, "[info]:");
 18        break;
 19    case 2:
 20        strcpy(s, "[warn]:");
 21        break;
 22    case 3:
 23        strcpy(s, "[erro]:");
 24        break;
 25    default:
 26        strcpy(s, "[info]:");
 27        break;
 28    }
 29
 30
 31    m_mutex.lock();
 32
 33    //更新现有行数
 34    m_count++;
 35
 36    //日志不是今天或写入的日志行数是最大行的倍数
 37    //m_split_lines为最大行数
 38    if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0)
 39    {
 40        char new_log[256] = {0};
 41        fflush(m_fp);
 42        fclose(m_fp);
 43        char tail[16] = {0};
 44
 45        //格式化日志名中的时间部分
 46        snprintf(tail, 16, "%d_%02d_%02d_", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday);
 47
 48        //如果是时间不是今天,则创建今天的日志,更新m_today和m_count
 49        if (m_today != my_tm.tm_mday)
 50        {
 51            snprintf(new_log, 255, "%s%s%s", dir_name, tail, log_name);
 52            m_today = my_tm.tm_mday;
 53            m_count = 0;
 54        }
 55        else
 56        {
 57            //超过了最大行,在之前的日志名基础上加后缀, m_count/m_split_lines
 58            snprintf(new_log, 255, "%s%s%s.%lld", dir_name, tail, log_name, m_count / m_split_lines);
 59        }
 60        m_fp = fopen(new_log, "a");
 61    }
 62
 63    m_mutex.unlock();
 64
 65    va_list valst;
 66    //将传入的format参数赋值给valst,便于格式化输出
 67    va_start(valst, format);
 68
 69    string log_str;
 70    m_mutex.lock();
 71
 72    //写入内容格式:时间 + 内容
 73    //时间格式化,snprintf成功返回写字符的总数,其中不包括结尾的null字符
 74    int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %s ",
 75                     my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday,
 76                     my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s);
 77
 78    //内容格式化,用于向字符串中打印数据、数据格式用户自定义,返回写入到字符数组str中的字符个数(不包含终止符)
 79    int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst);
 80    m_buf[n + m] = '\n';
 81    m_buf[n + m + 1] = '\0';
 82
 83    log_str = m_buf;
 84
 85    m_mutex.unlock();
 86
 87    //若m_is_async为true表示异步,默认为同步
 88    //若异步,则将日志信息加入阻塞队列,同步则加锁向文件中写
 89    if (m_is_async && !m_log_queue->full())
 90    {
 91        m_log_queue->push(log_str);
 92    }
 93    else
 94    {
 95        m_mutex.lock();
 96        fputs(log_str.c_str(), m_fp);
 97        m_mutex.unlock();
 98    }
 99
100    va_end(valst);
101}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值