10 日志系统(下)

news2024/11/14 18:37:01

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_log
	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(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)
	{
		//设置写入方式flag
		m_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_count
		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
		{
			//超过了最大行,在之前的日志名基础上加后缀,m_count/m_split_lines
			snprintf(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.coloradmin.cn/o/840005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android 刷新与显示

目录 屏幕显示原理&#xff1a; 显示刷新的过程 VSYNC机制具体实现 小结&#xff1a; 屏幕显示原理&#xff1a; 过程描述&#xff1a; 应用向系统服务申请buffer 系统服务返回一个buffer给应用 应用开始绘制&#xff0c;绘制完成就提交buffer&#xff0c;系统服务把buffer数据…

第三章 CUDA编译器环境配置篇

cuda教程目录 第一章 指针篇 第二章 CUDA原理篇 第三章 CUDA编译器环境配置篇 第四章 kernel函数基础篇 第五章 kernel索引(index)篇 第六章 kenel矩阵计算实战篇 第七章 kenel实战强化篇 第八章 CUDA内存应用与性能优化篇 第九章 CUDA原子(atomic)实战篇 第十章 CUDA流(strea…

Fatal error, can‘t open config file ‘/myredis/redis.conf‘: No such file or directory

在学习Redis到主从复制部分&#xff0c;进行相关练习&#xff0c;基本过程如下 1.首先将redis.conf文件cp到自建myredis文件夹中&#xff0c;并配置不同端口号的redis.conf redisXXXX.confd的配置内容如下 &#xff1a; include /myredis/redis.conf pidfile /var/run/redis_…

压力测试与测试工具jmeter的介绍

目录 一、性能指标 二、jmeter &#xff08;一&#xff09;JMeter 安装 &#xff08;二&#xff09;JMeter 压测示例 1、添加线程组 2、添加 HTTP 请求 3、添加监听器 4、启动压测&查看分析结果 &#xff08;三&#xff09;JMeter Address Already in use 错误解决 压力测…

Flutter运行app时向logcat输出当前打开的界面路径且点击可跳转

当一个项目大了目录文件多了&#xff0c;我们往往会为了找到一个文件花费大量的时间和精力&#xff0c;为了快捷方便的调试我们的项目&#xff0c;我们往往需要在打开app运行的时候需要知道当前打开的界面的文件在哪儿&#xff0c;我们这个代码就能快捷的知道我们app正在打开的…

《HeadFirst设计模式(第二版)》第五章代码——单例模式

代码文件目录&#xff1a; 初始版本&#xff1a; package Chapter5_SingletonPattern.origin;/*** Author 竹心* Date 2023/8/5**/public class Singleton {private static Singleton uniqueInstance;private Singleton(){}public static Singleton getInstance(){if(uniqueIn…

2023牛客暑期多校训练营6-A Tree

2023牛客暑期多校训练营6-A Tree https://ac.nowcoder.com/acm/contest/57360/A 文章目录 2023牛客暑期多校训练营6-A Tree题意解题思路代码 题意 解题思路 最大价值和这个数据范围&#xff0c;一眼 d p dp dp。 直接在树上并不好处理&#xff0c;问题是如何有效转化、处理…

黑马程序员SpringMVC练手项目

目录 1、需求 2、项目准备 pom.xml SQL jdbc.properties log4j.properties applicationContext.xml spring-mvc.xml web.xml 3、工作流程 4、难点 项目已经上传到gitee&#xff1a;https://gitee.com/xzl-it/my-projects 1、需求 SpringMVC项目练习&#xff1a;数…

每日一题——反转单链表

反转单链表 题目链接 下面主要介绍两种方法&#xff1a; 方法一&#xff1a; 利用三个指针变量进行反转 具体过程如图所示&#xff1a; 注意&#xff1a;循环的结束的条件为cur NULL而不是next NULL 实现代码&#xff1a; struct ListNode* reverseList(struct ListNode* …

STL容器适配器 -- stack和queue(使用+实现)(C++)

stack和queue stackstack的介绍stack的使用stack的实现 queuequeue的介绍queue的使用queue的实现 deque简单介绍deque&#xff08;双端队列&#xff09;双开口连续打引号的原因 deque底层结构deque的迭代器封装结构&#xff08;复杂&#xff09;deque的优缺点 栈和队列数据结构…

LLM reasoners 入门实验 24点游戏

LLM reasoners Ber666/llm-reasoners 实验过程 实验样例24games&#xff0c;examples/tot_game24&#xff0c;在inference.py中配置使用代理和open ai的api key。 首先安装依赖 git clone https://github.com/Ber666/llm-reasoners cd llm-reasoners pip install -e .然后…

JVM入门到精通

一、JVM概念 1.1、什么是JVM Java Virtual Machine&#xff1a;Java虚拟机&#xff0c;用来保证Java语言跨平台 Java虚拟机可以看做是一台抽象的计算机&#xff0c;如同真实的计算机那样&#xff0c;它有自己的指令集以及各种运行时内存区域 Java虚拟机与Java语言并没有必然…

Maven-搭建私有仓库

使用NEXUS REPOSITORY MANAGER 3在Windows上搭建私有仓库。 NEXUS REPOSITORY MANAGER 3 是一个仓库管理系统。 下载NEXUS3 官网上是无法下载的,所以网上搜nexus-3.18.1-01-win64就能搜到,下载即可。 安装NEXUS3 下载nexus-3.18.0-01-win64.zip至相应目录下(路径不要有中文)。 …

[Realtek sdk-3.4.14b]RTL8197FH-VG+RTL8812F WiFi开启访客网络之后无法扫描到SSID问题分析及解决方案

问题描述 realtek sdk-3.4.14b 开启访客网络之后,发现无法扫描到SSID,可以看到接口已经up,但是设备无法搜到WiFi热点 问题分析 查看网口状态 ifconfig查看wlan0-va0接口TX/RX的数据包都是0,表示没有发送或者接收到数据包,正常wifi启动之后,都会有Beacon包发出,也会接…

【Redis】——RDB快照

Redis 是内存数据库&#xff0c;但是它为数据的持久化提供了两个技术&#xff0c;一个是AOF日志&#xff0c;另一个是RDB快照&#xff1a; AOF 文件的内容是操作命令&#xff1b;RDB 文件的内容是二进制数据。 RDB 快照就是记录某一个瞬间的内存数据&#xff0c;记录的是实际…

Vue中,$forceUpdate()的使用

在Vue官方文档中指出&#xff0c;$forceUpdate具有强制刷新的作用。 那在vue框架中&#xff0c;如果data中有一个变量:age&#xff0c;修改他&#xff0c;页面会自动更新。 但如果data中的变量为数组或对象&#xff0c;我们直接去给某个对象或数组添加属性&#xff0c;页面是识…

JMeter 4.x 简单使用

文章目录 前言JMeter 4.x 简单使用1. 启动2. 设置成中文3. 接口测试3.1. 设置线程组3.2. HTTP信息请求头管理器3.3. 添加HTTP请求默认值3.4. 添加HTTP cookie 管理3.5. 添加http请求3.5.1. 添加断言 3.6. 添加监听器-查看结果树3.7. 添加监听器-聚合报告 4. 测试 前言 如果您觉…

TBB库中实现协程(coroutine)的源码说明

源码请见: https://github.com/oneapi-src/oneTBB/blob/master/src/tbb/co_context.h 在windows系统&#xff0c;TBB(也就是intel 的 oneTBB库)&#xff0c;通过windwos fiber(纤程)来实现协程(coroutine)。 创建一个协程,代码很简洁: inline void create_coroutine(corouti…

docker【安装、存储、镜像、仓库、网络、监控】

docker-0110.0.0.51docker-0210.0.0.52docker-0310.0.0.53 【1】docker安装 docker-01 [rootdocker-01 ~]# vim /etc/yum.conf [main] cachedir/var/cache/yum/$basearch/$releasever keepcache1 debuglevel2 logfile/var/log/yum.log exactarch1 obsoletes1 gpgcheck1 plugin…