10 日志系统(下)

news/2025/1/12 17:39:29/

10 日志系统(下)

本文内容

日志系统分为两部分,其一是单例模式与阻塞队列的定义,其二是日志类的定义与使用。
本篇将介绍日志类的定义与使用,具体的涉及到基础API,流程图与日志类定义,功能实现。
基础API,描述fputs,可变参数宏,fflush
流程图与日志类定义,描述日志系统整体运行流程,介绍日志类的具体定义。
功能实现,结合代码分析同步、异步写文件逻辑,分析超行、按天分文件和日志分级的具体实现。

基础API

fputs

#include <stdio.h>
int fputs(const char *str,FILE *stream);
  • str,一个数组,包含了要写入的以空字符终止的字符序列
  • stream,指向FILE对象的指针,该FILE对象标识了要被写入字符串的流。

可变参数宏__VA_ARGS__

__VA_ARGS__是一个可变参数的宏,定义时宏定义中参数列表的最后一个参数为省略号,在实际使用时会发现有时会加##,有时又不加。

//最简单的定义
#define my_print1(...) printf(__VA_ARGS__)//搭配va_list的format使用
#define my_print2(format,...) printf(format,__VA_ARGS__)
#define my_print3(format,...) printf(format,##__VA_ARGS__)

__VA__ARGS__宏前面加上##的作用在于,当可变参数的个数为0时,这里printf参数列表中的##会把前面多余的“,”去掉,否则会编译出错,建议使用后面这种,使得程序更加健壮。

fflush

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

fflush()会强迫将缓冲区内的数据写回参数stream指定的文件中,如果参数stream为NULL,fflush(()会将所有打开的文件数据更新。
在使用多个输出函数连续进行多次输出到控制台时,有可能下一个数据再上一个数据还没输出完毕,还在输出缓冲区中时,下一个printf就把另一个数据加入输出缓冲区,结果冲掉了原来的数据,出现输出错误哦。
再printf()后加上fflush(stdout);强制马上输出到控制台,可以避免出现上述错误。

流程图与日志类定义

流程图

日志文件

  • 局部变量的懒汉模式获取实例
  • 生成日志文件,并判断同步和异步写入方式

同步

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

异步

  • 判断是否分文件
  • 格式化输出内容,将内容写入阻塞队列,创建一个写线程,从阻塞队列取出内容写入日志文件.

在这里插入图片描述

日志类定义

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

  • 公有的实例获取方法
  • 初始化日志文件方法
  • 异步日志写入方法,内部调用私有异步方法
  • 内容格式化方法
  • 刷新缓冲区
class Log
{
public://C++11以后,使用局部变量懒汉不用加锁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);//异步写日志公有方法,调用私有方法async_write_logstatic 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(sigle_log)){m_mutex.lock();fputs(single_log.c_str(),m_fp);m_mutex.unlock();}}private:char dir_name[128]; //路径名char log_name[128]; //log文件名int m_split_lines; //日志最大行数int m_log_buf_size;//日志缓冲区大小long long m_count;//日志行数记录int m_today;//按天分文件,记录当前时间是哪一天FILE *m_fp;//打开log的文件指针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,则为同步,否则异步。

//异步需要设置阻塞队列的长度,同步不需要设置
bool init(const char *file_name,int log_buf_size,int split_lines,int max_queue_size)
{//如果设置了max_queue_size,则设置为异步if(max_queue_size>=1){//设置写入方式flagm_is_async=true;//创建并设置阻塞队列长度m_log_queue=new block_queue<string>(max_queue_size);pthread_t tid;//flush_log_thread为回调函数,这里表示创建线程异步写日志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=strchr(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,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,输出系统的错误信息

项目中实际使用了Debug、Info、Error三种。
超行、按天分文件逻辑,具体的:
日志写入前会判断当前day是否为创建日志的时间,行数是否超过最大行限制

  • 若为创建日志时间,写入日志,否则按当前时间创建新log,更新创建时间和行数
  • 若行数超过最大行限制,在当前日志的末尾加count/max_lines为后缀创建新log

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

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++;//日志不是今天或写入的日志是最大行的倍数//m_split_lines为最大行数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);//如果是时间不是今天,则创建今天的日志,更新m_today和m_countif(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{//超过了最大行,在之前的日志名基础上加后缀,m_count/m_split_linessnprintf(new_log,255,"%s%s%s.%11d",dir_name,tail,log_name,m_count/m_split_lines);}m_fp=fopen(new_log,"a");}m_mutex.unlock();va_list valst;//将传入的format参数赋值给valst,便于格式化输出va_start(valst,format);string log_str;m_mutex.lock();//写入内容格式:时间+内容//时间格式化,snprintf成功返回写字符的总数,其中不包括结尾的null字符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);//内容格式化,用于向字符串中打印数据、数据格式用户自定义,返回写入到字符数组str中的字符个数(不包含终止符)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();//若m_is_async为true表示异步,默认为同步//若异步,则将日志信息加入阻塞队列,同步则加锁向文件中写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);
}

http://www.ppmy.cn/news/1008860.html

相关文章

VX-API-Gateway开源网关技术的使用记录

VX-API-Gateway开源网关技术的使用记录 官网地址 https://mirren.gitee.io/vx-api-gateway-doc/ VX-API-Gateway(以下称为VX-API)是基于Vert.x (java)开发的 API网关, 是一个分布式、全异步、高性能、可扩展、轻量级的可视化配置的API网关服务官网下载程序zip包 访问 https:/…

笙默考试管理系统-MyExamTest(27)

笙默考试管理系统-MyExamTest&#xff08;27&#xff09; 目录 一、 笙默考试管理系统-MyExamTest 二、 笙默考试管理系统-MyExamTest 三、 笙默考试管理系统-MyExamTest 四、 笙默考试管理系统-MyExamTest 五、 笙默考试管理系统-MyExamTest 笙默考试管理系统-MyEx…

嵌入式开发学习(STC51-15-红外遥控)

内容 使用外部中断功能&#xff0c;使按下红外遥控器&#xff0c;将对应键值编码数据解码后通过数码管显示 红外遥控介绍 红外线简介 人的眼睛能看到的可见光按波长从长到短排列&#xff0c;依次为红、橙、黄、绿、青、蓝、紫&#xff1b; 其中红光的波长范围为 0.62&…

Django架构图

1. Django 简介 基本介绍 Django 是一个由 Python 编写的一个开放源代码的 Web 应用框架 使用 Django&#xff0c;只要很少的代码&#xff0c;Python 的程序开发人员就可以轻松地完成一个正式网站所需要的大部分内容&#xff0c;并进一步开发出全功能的 Web 服务 Django 本身…

使用七牛云、阿里云、腾讯云的对象存储上传文件

说明&#xff1a;存在部分步骤省略的情况&#xff0c;请根据具体文档进行操作 下载相关sdk composer require qiniu/php-sdkcomposer require aliyuncs/oss-sdk-php composer require alibabacloud/sts-20150401composer require qcloud/cos-sdk-v5 composer require qcloud_s…

Redis | 主从模式

Redis | 主从模式 1. 简介 Redis主从模式&#xff08;Replication&#xff09;是Redis提供的一种数据备份和高可用性解决方案。通过主从复制&#xff0c;可以将一个Redis服务器的数据复制到其他多个从服务器&#xff0c;从而实现数据的备份和读写分离&#xff0c;提高系统的性…

Spring Boot读取yml或者properties配置信息

文章目录 Spring Boot读取yml或者properties配置信息方法一&#xff1a;Value获取基本信息&#xff0c;适用于少量信息方法二&#xff1a;通过注解ConfigurationProperties(prefix "spring.datasource")方法三&#xff1a;通过api Environment Spring Boot读取yml或…

Android查漏补缺

&#xff08;1&#xff09;android apk编译步骤 ①&#xff0c;aapt工具生成.R文件 ②&#xff0c;javac编译为字节码 ③&#xff0c;dex工具打包为.dex文件 ④&#xff0c;aapt工具打包资源未见为.ap文件 ⑤&#xff0c;apkbuilder工具打包为apk文件 ⑥&#xff0c;keyt…