Linux_实现简易日志系统

       

目录

1、认识可变参数

2、解析可变参数

3、打印可变参数

3.1 va_list

3.2 va_start

3.3 va_arg

3.4 va_end 

3.5 小结 

4、实现日志 

4.1 日志左半部分 

4.2 日志右半部分 

4.3 日志的存档归类 

结语 


前言:

        在Linux下实现一个日志系统,该日志系统主要用于打印和记录程序的格式化信息,还可以对信息做分析处理,比如把信息分为五种类型:正常运行信息、测试信息,警告信息,错误信息、致命信息,当然还可以显示信息产生的具体时间,并且能够对这些信息进行存档归类。

1、认识可变参数

        因为日志系统记录的是程序往显示器上打印的信息,所以肯定离不开printf系列函数,可以得知他的底层肯定是调用了printf系列函数来实现的,而这个过程会涉及到可变参数的传递,所以实现日志系统前要认识可变参数。

        可变参数的形式如下:

int printf(const char *format, ...);
//第二个参数是三个点,...表示可变参数,即可以传多个实参给到printf

        所以可变参数表示可以接收任意数量的参数。

2、解析可变参数

        当我们拿到了一个可变参数,如何对其进行分析拿到具体的每一个值呢?要解析可变参数必须从函数栈帧的角度来理解,示意图如下: 

        从上图可得,只要拿到了format处的地址,后续就可以通过下一步的操作拿到format+1处的内容了,然后让指针继续“向上走”就可以遍历整个可变参数了。以上的逻辑也规定了一点:若想使用可变参数则必须得有一个具体参数,这个规则体现在下面代码处。

3、打印可变参数

        有了上述的解析规则,则可以用代码打印可变参数的每个值,代码如下:

#include <iostream>
#include <stdarg.h>using namespace std;void print(int n,.../*6 8 9 2 3*/)
{va_list s;va_start(s,n);while (n--){printf("%d\n",va_arg(s,int));}va_end(s);}int main()
{print(5,6,8,9,2,3);return 0;
}

         运行结果:

        从结果可以看到,打印的顺序和上述分析的逻辑一模一样。下面就来解释上述代码中出现的4个字段:va_list、va_start、va_arg、va_end。

3.1 va_list

        va_list可以看成是一个类型,用他定义的变量就像是创建一个char*的指针。

3.2 va_start

        va_start是一个宏函数,他的作用是对va_list定义的变量进行初始化,他的参数介绍如下:

void va_start(va_list ap, last);
// ap表示用va_list定义的变量
// last表示可变参数的前一个固定参数

        比如上述代码中可变参数的前一个参数是n,所以va_start的第二个参数是n,这一动作就是上述逻辑中让指针定位到format处(这也解释了为什么可变参数前面必须得有固定参数,因为要让指针定位)。

3.3 va_arg

        va_arg也是一个宏函数,他的功能是拿到下一个参数的值(注意是拿下一个,而不是拿当前位置的值),其参数介绍如下:

type va_arg(va_list ap, type)
//第一个参数是va_list定义的变量
//第二个参数表示ap指向下一个参数的类型
//返回下一个参数的值

         从当前地址开始,通过计算拿到下一个参数的地址,然后在根据type类型,对该地址解引用拿到该参数的值并返回,此时会更新ap的位置,以便继续遍历后续的列表。

3.4 va_end 

         va_end是对va_list创建的变量进行清理工作,他的参数介绍如下:

void va_end(va_list ap);
//ap表示清理的目标

3.5 小结 

        按照va_list(创建变量)、va_start(初始化变量)、va_arg(遍历列表)、va_end(清理工作)这四个步骤,就可以拿到可变列表中的每一个值了。

4、实现日志 

        日志信息实际上就是一串字符,日志的目的就是打印到屏幕上给用户观看(或者存档归类方便查看),只不过日志信息更加规范且完善,因为他还记录了信息的时间和类型,所以可以把日志信息分成两个部分:左半部分和右半部分,其中右半部分就是程序的输出信息,而左半部分是记录信息的类型和时间。

        信息类型:信息分为五种类型:info,debug、warning、error、fatal,让用户手动对信息进行分类。

        记录时间:可以用time函数得到一个时间戳,然后再把该时间戳传给localtime函数,localtime会返回一个结构体,里面包含了年月日时分秒等精确时间。

4.1 日志左半部分 

        日志左半部分由信息类型和时间构成,代码如下:

#include <time.h>
#include <iostream>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>using namespace std;
#define SIZE 1024
//对信息类型进行define
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4class log
{
public:log(){}const char* option(int point)//手动选择信息类型{switch (point){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void logprint(int point, const char *format, ...){//日志左边字符串char leffbuff[SIZE];time_t t = time(nullptr);struct tm *pt = localtime(&t);snprintf(leffbuff, SIZE, "[%s][%d-%d-%d-%d-%d-%d]",option(point), pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour, pt->tm_min, pt->tm_sec);printf("%s\n",leffbuff);}~log(){}private:
};int main()
{log l;l.logprint(Debug,nullptr);return 0;
}

        运行结果:

        上述中将功能都写进类log内,目的是更好的对日志系统进行封装。

4.2 日志右半部分 

        日志右半部分就是程序格式化的信息,程序的格式化信息一般由printf系列的函数完成,所以要想在logprint内完成printf操作,则必须传递可变参数,因此可以用一个功能极其强大的接口vsnprintf函数,该函数介绍如下:

int vsnprintf(char *str, size_t size, const char *format, va_list ap);
//str表示数组首元素地址,将printf的结果写进str数组中//size表示要写入数组内容的大小,并且在末尾处自动添加\0,即最多写size-1个字符,
//他会自动留个位置给到\0//format表示格式化的字符串//ap表示va_list创建的变量

        所以可以用va_list定义的变量指向可变参数列表,然后把该变量传给vsnprintf,这样就等同于将可变参数传给vsnprintf,则vsnprintf可以将可变参数的值打印出来,只不过不是打印到屏幕上而是写进str内,而这个str就是日志右半部分的字符串,最后在合并左右部分就得到最终的日志信息了。

        代码如下:

#include <time.h>
#include <iostream>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>using namespace std;
#define SIZE 1024
//对信息类型进行define
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4class log
{
public:log(){}const char* option(int point)//手动选择信息类型{switch (point){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void logprint(int point, const char *format, ...){//日志左边字符串char leffbuff[SIZE];time_t t = time(nullptr);struct tm *pt = localtime(&t);snprintf(leffbuff, SIZE, "[%s][%d-%d-%d-%d-%d-%d]",option(point), pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour, pt->tm_min, pt->tm_sec);//日志右边字符串char rightbuff[SIZE];va_list s;va_start(s,format);vsnprintf(rightbuff,SIZE,format,s);//合并成为最终日志信息char logmessege[SIZE*2];snprintf(logmessege,SIZE*2,"%s%s\n",leffbuff,rightbuff);printf("%s",logmessege);}~log(){}private:
};int main()
{log l;int a = 10;int b = 12;l.logprint(Debug,"a=%d,b=%d",a,b);return 0;
}

         运行结果:

4.3 日志的存档归类 

        上面实现的日志只能打印在屏幕上,而日志的主要功能是可以存档归类,方便后续查看,所以上面代码在打印日志这一步时可以做一个判断:1、打印到屏幕,2、打开文件将日志信息写入文件中,3、写入文件时根据文件类型进行文件归类,因此可以在log类中加上两个成员变量,一个是指定日志存放的路径,一个是输出方式,具体成员如下:

private:string path;//即路径目录int printmethod;//输出方式:1、显示器 2、文件 3、文件归类

         完整日志代码如下:

#include <time.h>
#include <iostream>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>using namespace std;
#define SIZE 1024
//对信息类型进行define
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
//三种方式
#define Screen 1
#define Onefile 2
#define Classfile 3 #define logfile "log.txt"class log
{
public:log()//初始化成员变量{path = "./log/";printmethod = 2;//选取方式2}const char* option(int point)//手动选择信息类型{switch (point){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}//打印日志信息的三种方式具体实现void printscreen(const char* logmessege){printf("%s",logmessege);              }void printonefile(const string& filename,const char* logmessege){string logpath = path+filename;int fd = open(logpath.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd<0){perror("open");exit(-1);}//写write(fd,logmessege,strlen(logmessege));close(fd);}void printclassfile(int point, const char* logmessege){string logpath = logfile;(logpath+='.')+=option(point);printonefile(logpath,logmessege);}//选取打印日志的方式void printflow(int point, const char* logmessege){switch (printmethod){case Screen:printscreen(logmessege);//打印屏幕break;case Onefile:printonefile(logfile,logmessege);//写入文件break;case Classfile://实际上就是改了文件名然后复用printonefileprintclassfile(point,logmessege);//文件归类break;default:break;}}void logprint(int point, const char *format, ...){//日志左边字符串char leffbuff[SIZE];time_t t = time(nullptr);struct tm *pt = localtime(&t);snprintf(leffbuff, SIZE, "[%s][%d-%d-%d-%d-%d-%d]",option(point), pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour, pt->tm_min, pt->tm_sec);//日志右边字符串char rightbuff[SIZE];va_list s;va_start(s,format);vsnprintf(rightbuff,SIZE,format,s);//合并成为最终日志信息char logmessege[SIZE*2];snprintf(logmessege,SIZE*2,"%s%s\n",leffbuff,rightbuff);//printf("%s\n",logmessege);printflow(point, logmessege);//调用具体输出方法}~log(){}private:string path;//即路径目录int printmethod;//输出方式:1、显示器 2、文件 3、文件归类
};int main()
{log l;int a = 10;int b = 12;l.logprint(Debug,"a=%d,b=%d",a,b);return 0;
}

        运行结果:

        从结果可以看到,方式2是将格式化的内容写进文件里,但是在此之前要先在当前目录下创建一个log目录,因为成员变量path初始化的内容是"./log/",表示日志文件都存放在这个路径下。 

结语 

         以上就是关于Linux下实现日志系统的讲解,实现日志系统的核心点在于了解可变参数的使用,以及对字符串的操作运用。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/42494.shtml

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

相关文章

ffmpeg图片视频编辑器工具的安装与使用

title: ffmpeg图片视频编辑器工具的安装与使用 tags: [ffmpeg, 图片, 音频, 视频, 工具, 流媒体] categories: [工具, ffmpeg] FFmpeg是一个开源的命令行工具&#xff0c;广泛用于处理视频和音频文件&#xff0c;包括转换格式、剪辑、混流、解码、编码等。以下是一些基本的FFmp…

java项目总结6

目录 1.双列集合 2.map的三种遍历方式&#xff1a; 1.键找值 2.键值对 3.lambda遍历map 3.HashMap 例子&#xff1a;统计字符出现次数 4.LinkedHashMap 5.TreeMap 6.可变参数 7.Collections: 1.双列集合 双列集合特点&#xff1a; 定义Map<String&#xff0c;St…

Linux中的管道符‘|‘以及SQL(DQL,DCL)

ls 指令 语法&#xff1a; ls [选项][目录或文件] 功能&#xff1a; 对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。 常用选项&#xff1a; -a 列出目录下的所有文件&#xff0c;包括以 . 开头的隐含文件。 -…

自注意力机制和多头注意力机制区别

Ref&#xff1a;小白看得懂的 Transformer (图解) Ref&#xff1a;一文彻底搞懂 Transformer&#xff08;图解手撕&#xff09; 多头注意力机制&#xff08;Multi-Head Attention&#xff09;和自注意力机制&#xff08;Self-Attention&#xff09;是现代深度学习模型&#x…

昇思25天学习打卡营第19天|Pix2Pix实现图像转换

1. 学习内容复盘 Pix2Pix概述 Pix2Pix是基于条件生成对抗网络&#xff08;cGAN, Condition Generative Adversarial Networks &#xff09;实现的一种深度学习图像转换模型&#xff0c;该模型是由Phillip Isola等作者在2017年CVPR上提出的&#xff0c;可以实现语义/标签到真实…

点胶系统实战1-项目介绍

准备实战开发如下图的多轴点胶系统实战课程&#xff0c;内容设计界面开发、运动板块开发、任务管理、点胶的控制等。我们将和进入这个领域的初学者门一起进步。 有感兴趣的小伙伴&#xff0c;可以关注点赞&#xff0c;或评论区反馈你们的重点关注的内容&#xff0c;那些部分我…

Postman使用指南①网页版使用

postman官网地址&#xff1a;Postman API Platform 进入后点击右上角免费注册&#xff0c;注册后登录 登录之后即可在网页使用&#xff0c;无需下载

线上问题---反思与回顾

线上问题一&#xff1a;麦哲伦2.0 人群配置不生效 发现背景&#xff1a;产品发现三层模型部分计划个别测试计划圈选人群特征与数仓统计数据的人群不一致&#xff0c;向值班人员反馈 根因定位&#xff1a; &#xff08;1&#xff09;用户配置三层模型计划时&#xff0c;配置单…

RNN 交叉熵

RNN善于处理时序 序列数据 简单RNN 展开就是 LSTM 遗忘门f_t决定上期记忆保留多少 隐藏层 在神经网络中&#xff0c;隐藏层指的是除了输入层和输出层之外的层&#xff0c;它们的输出不会直接用于网络的最终输出&#xff0c;而是作为中间步骤用于提取和转换数据。因此&#x…

【网络安全】实验五(身份隐藏与ARP欺骗)

一、本次实验的实验目的 &#xff08;1&#xff09;了解网络攻击中常用的身份隐藏技术&#xff0c;掌握代理服务器的配置及使用方法 &#xff08;2&#xff09;通过实现ARP欺骗攻击&#xff0c;了解黑客利用协议缺陷进行网络攻击的一般方法 二、搭配环境 打开三台虚拟机&#…

SQL Server特性

一、创建表 在sql server中使用create table来创建新表。 create table Customers( id int primary key identity(1,1), name varchar(5) ) 该表名为Customers其中包含了2个字段&#xff0c;分别为id&#xff08;主键&#xff09;以及name。 1、数据类型 整数类型&#xff…

Towards Accurate and Robust Architectures via Neural Architecture Search

基于网络架构搜索的准确性与鲁棒性结构研究 论文链接&#xff1a;https://arxiv.org/abs/2405.05502 项目链接&#xff1a;未开源 Abstract 为了保护深度神经网络免受对抗性攻击&#xff0c;对抗性训练因其有效性而受到越来越多的关注。然而&#xff0c;对抗训练的准确性和鲁…

扩散模型笔记

长参数“T”决定了生成全噪声图像所需的步长。在本文中&#xff0c;该参数被设置为1000&#xff0c;这可能显得很大。我们真的需要为数据集中的每个原始图像创建1000个噪声图像吗?马尔可夫链方面被证明有助于解决这个问题。由于我们只需要上一步的图像来预测下一步&#xff0c…

vue2 webpack使用optimization.splitChunks分包,实现按需引入,进行首屏加载优化

optimization.splitChunks的具体功能和配置信息可以去网上自行查阅。 这边简单讲一下他的使用场景、作用、如何使用&#xff1a; 1、没用使用splitChunks进行分包之前&#xff0c;所有模块都揉在一个文件里&#xff0c;那么当这个文件足够大、网速又一般的时候&#xff0c;首…

【C++】cout.self()函数

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文作为 JohnKi 学习笔记&#xff0c;借鉴了部分大佬案例 &#x1f4e2;未来很长&#…

工地/矿区/电力/工厂/环卫视频智能安全监控反光衣AI检测算法的原理及场景应用

一、引言 随着科技的快速发展&#xff0c;特别是在智能交通和安全生产领域&#xff0c;对于夜间或弱光环境下的人员识别和安全监控需求日益凸显。反光衣作为一种重要的安全装备&#xff0c;被广泛应用于道路施工、工地作业、夜间巡逻、安全生产等场景&#xff0c;旨在提高人员的…

Meerkat:第一个统一视听空间和时间定位的MLLM

大型语言模型&#xff08;LLMs&#xff09;在各种自然语言处理任务中表现出色&#xff0c;达到了理解和推理能力的人类水平精度。此外&#xff0c;借助新兴的指令微调范式&#xff0c;这些语言模型可以被赋予遵循开放式自然语言指令的能力&#xff0c;甚至可以与其他模态&#…

机器学习第四十六周周报 FMP

文章目录 week46 FMP摘要Abstract1. 题目2. Abstract3. FMP3.1 优化框架3.2 优化器 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程 5. 结论6.代码复现1. FMP2. fairGNN小结参考文献 week46 FMP 摘要 本周阅读了题为Chasing Fairness in Graphs: A GNN Architecture Per…

【Spring Cloud】微服务的简单搭建

文章目录 &#x1f343;前言&#x1f384;开发环境安装&#x1f333;服务拆分的原则&#x1f6a9;单一职责原则&#x1f6a9;服务自治&#x1f6a9;单向依赖 &#x1f340;搭建案例介绍&#x1f334;数据准备&#x1f38b;工程搭建&#x1f6a9;构建父子工程&#x1f388;创建父…

leetcode每日一题-3033. 修改矩阵

题目描述&#xff1a; 解题思路&#xff1a;简单题目&#xff0c;思路非常直接。对列进行遍历&#xff0c;记录下最大值&#xff0c;然后再遍历一遍&#xff0c;把-1替换为最大值。需要注意的是进行列遍历和行遍历是不同的。 官方题解&#xff1a; class Solution { public:v…