初识Linux · 日志编写

目录

前言:

日志的简单说明

编写日志


前言:

在线程池部分我们纵观全文,可以发现全文有很多很多的IO流,看起来还是差点意思的,而我们今天提到的日志,是在今后的代码编写中会经常接触,或者说在这之前,我们也接触过日志,不过我们没有注意而已,比如平常常用的电脑,强制关机什么的都会在日志里面。

接下来,我们将简单介绍一下日志的相关知识,然后进行编写。


日志的简单说明

对于日志来说,我们平常使用的日志有几个等级的,第一个等级是DEBUG,代表调试信息,第二个等级是INFO,代表普通信息,第三个等级是WARNING,代表警告,部分警告我们是不用过多在意的,比如在最新的编译器上使用头文件unistd.h的话,是有警告的,但是我们不必在意,第四个等级是ERROR,代表出错了,这种错误呢我们平时编写高级语言的代码的时候,出现了数据的越界啊,除0什么的都会出现ERROR,最后一个等级就十分危险了,大部分人到到现在应该是没有见到过的,是FATAL,代表的是致命的。

那么对于日志来说,我们需要时间吧?我们需要等级吧?文件名吧?发生了报错需要指定具体的行号吧?最后还有一个打印出来的信息吧?

由以上可得信息所需要的成员变量还是非常多的,加上有些函数我们还没有具体了解,所以其实编写日志也算是一个小小的挑战了。

那么废话不多说,我们直接进入编写日志的环节。


编写日志

编写日志之前,由于日志的有多个不同的等级,所以我们不妨使用枚举变量用来表示日志的不同等级:

enum grade
{DEBUG = 1,INFO,WARNING,ERROR,FATAL
};

对于日志,我们也分析了需要当前时间,文件名,当前行号,进程pid也可以来一下,打印的信息,日志等级,那么我们可以用个结构体将它们全部组合在一起:

struct log_message
{std::string _level;pid_t _id;std::string _filename;int _filenumber;std::string _cur_time;std::string message_info;
};

好了,现在暂时有两个问题,第一个是,我们如何获取到当前的时间?方法肯定有很多种,这里我们推荐函数localtime。第二个是level我们是使用的枚举类型用来表示的,那么我们肯定需要将整数转换成对应的string类型。

我们不妨先来解决简单的,我们将整数转换为我们需要的日志等级信息,我们可以使用switch函数:

std::string GradeToString(int level)
{switch(level){case DEBUG:return "DEBUG";case INFO:return "INFO";case WARNING:return "WARNING";case ERROR:return "ERROR";case FATAL:return "FATAL";default:return "UNKNOWN";}
}

这段代码唯一的难点可能就是很久没有用到switch了导致的生疏而已。

那么对于第二个问题,我们先man localtime:

对于这个函数而言,返回值类型是结构体tm指针,所需要的头文件是time.h,那么在C++中,自然就是对应的ctime头文件了,对应的参数是time_t类型的变量,实际上就是time函数的返回值。

那么结构体tm的组成是这样的:

           struct tm {int tm_sec;    /* Seconds (0-60) */int tm_min;    /* Minutes (0-59) */int tm_hour;   /* Hours (0-23) */int tm_mday;   /* Day of the month (1-31) */int tm_mon;    /* Month (0-11) */int tm_year;   /* Year - 1900 */int tm_wday;   /* Day of the week (0-6, Sunday = 0) */int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */int tm_isdst;  /* Daylight saving time */};

year对应的就是年,mon对应的就是月,mday代表的是这个月的第几天,也就是日,hour min sec对应的就是时分秒了,对于其他成员变量我们看旁边的注释也能清楚。

那么get当前时间也挺简单的了就,但是为了方便起见,我们可以返回一个字符串,不然到时候打印的时候我们还需要转格式就有点麻烦了:

std::string GetCurTime()
{const time_t t = time(nullptr);struct tm* cur_time = localtime(&t);char buffer[128];snprintf(buffer,sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",cur_time->tm_year + 1990,cur_time->tm_mon + 1,cur_time->tm_mday,cur_time->tm_hour,cur_time->tm_min,cur_time->tm_sec);return buffer;
}

当然了,为了格式的美观,我们可以在除了年的所有格式里面加上02d,以及因为tm_year的值是减去了1990的,所以我们加上,month同理。

此时,时间的问题就解决了。

那么对于日志来说,可以打印在显示器上面,也可以打印到文件里面,所以我们不妨对于日志类设置一个变量,用来表示打印在哪里,一个用来表示文件,一个用来表示显示器文件:

const std::string default_logfile = "./log.txt";
#define SCREEN_TYPE 1
#define FILE_TYPE 2class log
{
public:log(const std::string &logfile = default_logfile): _log_file(logfile), _type(SCREEN_TYPE){}void ModifyType(int type){_type = type;}private:int _type;std::string _log_file;
};

对于日志的基本框架也搭建好了,现在我们需要考虑的有以下几个逻辑,如何将日志打印到显示器,如何将日志打印到文件里面?

对于以上的逻辑,我们不妨先将我们的信息封装好,在封装信息的这个过程中,我们会用到可变参数,以及用到新的宏和新的类型,我们先来简单介绍一下新的知识,va_list以及va_start,va_end:

其中va_list是通过 __gnuc_va_list重命名过来的,对于va_start和va_end都是一个宏。

对于va_list来说,是获取可变参数列表里面的可变参数,通过是使用一个内部指针指向它。

对于va_start来说,第一个参数是va_list的类型,对于第二个参数往往是可变参数列表的前一个类型。

对于va_end来说是用来清理va_list变量的一个宏。

而要注意的是,va_list这种都是C语言标准库里面的,并不是C++语言直接提供的

现在我们不妨反问一下,如果没有va_list,我们如何访问可变参数列表?

这里使用到的函数栈帧的知识,函数的参数是从右往左进行压栈的,所以如果我们想要访问参数,需要一个指针,并且确切的知道可变参数列表里面参数的类型,便于在栈中易于通过指针运算找到每一个参数。

当然,这种方法是非常不安全也是非常不具有移植性的,非常不推荐,咱们了解一下即可。

有了va_list之后,我们对于可变参数的处理就十分简单了,不过我们现在知道了可变参数可以有va_list获取之后,我们应该如何处理呢?这里就需要使用到函数vnsprintf了,函数原型为:

int vnsprintf(char *str, size_t size, const char *format, va_list ap);
  • char *str:指向存储结果字符串的缓冲区的指针。
  • size_t size:缓冲区的大小,即str可以容纳的最大字符数(包括空字符'\0')。
  • const char *format:格式化字符串,用于指定输出格式。这个字符串可以包含普通字符和格式说明符(如%d%s等),这些格式说明符会被va_list中的相应参数替换。
  • va_list ap:一个可变参数列表,包含了要格式化的数据。这个列表通常使用va_start宏初始化,使用va_arg宏访问,最后使用va_end宏清理。
    void logMessage(const std::string &filename, int filenumber, int level, const char *format, ...){log_message lg;lg._level = GradeToString(level);lg._id = getpid();lg._filenumber = filenumber;lg._filename = filename;lg._cur_time = GetCurTime();va_list va;va_start(va, format);char log_info[1024];vsnprintf(log_info, sizeof(log_info), format, va);va_end(va);lg._message_info = log_info;// 开始打印日志FlushLog(lg);}

信息处理完毕了,我们现在开始实现打印日志的逻辑:

    void FlushLog(log_message &lg){pthread_mutex_lock(&gmutex);if (_type == SCREEN_TYPE)FlushScreen(lg);elseFlushFile(lg);pthread_mutex_unlock(&gmutex);}

对于打印来说,打印访问的资源都是log_message,所以我们是有必要加锁的,加一把全局的锁就可以了。如果不加锁会发生打印错乱的现象,现在就是最后两个函数了,一个打印到显示器上:

    void FlushScreen(log_message &lg){printf("[%s][%d][%s][%d][%s] %s",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._cur_time.c_str(),lg._message_info.c_str());}
    void FlushFile(log_message &lg){std::ofstream out(_log_file, std::ios::app);if (!out.is_open())return;char logtxt[2048];snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._cur_time.c_str(),lg._message_info.c_str());out.write(logtxt, strlen(logtxt));out.close();}

以上是日志的基本编写,别急,我们不妨测试一下:

// 测试日志编写
int main()
{log().logMessage("test filename1",1, DEBUG, "Hello world %d\n", 1200);log().logMessage("test filename2",2, INFO, "Hello world %d\n", 1201);log().logMessage("test filename3",3, FATAL, "Hello world %d\n", 1202);log().logMessage("test filename4",4, ERROR, "Hello world %d\n", 1203);log().logMessage("test filename5",5, DEBUG, "Hello world %d\n", 1204);log().logMessage("test filename6",6, FATAL, "Hello world %d\n", 1205);return 0;
}

只能说日志编写太不错了。

但是但是,还是差点意思,我们还要定义匿名对象,还要手动的写文件名,文件行号,实在麻烦,所以我们可以使用宏进行封装,这里使用宏的时候,如果涉及了续航符,编译器是不会省略的,但是之前的printf那些编译器是会省略的。这点需要注意。

log lg;#define LOG(Level, Format, ...)                                        \do                                                                 \{                                                                  \lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \} while (0)
#define EnableScreen()          \do                          \{                           \lg.ModifyType(SCREEN_TYPE); \} while (0)
#define EnableFILE()          \do                        \{                         \lg.ModifyType(FILE_TYPE); \} while (0)

在class log的最后面,我们不妨定义一个宏,有点函数的意思,对于宏__FILE__ __LINE__的意思是当前文件的名字和当前行号,后面的就是等级,格式了,对于##代表的是如果没有可变参数,那么自动忽视__VA_ARGS__。

int main()
{log().logMessage("test filename1",1, DEBUG, "Hello world %d\n", 1200);log().logMessage("test filename2",2, INFO, "Hello world %d\n", 1201);// log().logMessage("test filename3",3, FATAL, "Hello world %d\n", 1202);// log().logMessage("test filename4",4, ERROR, "Hello world %d\n", 1203);// log().logMessage("test filename5",5, DEBUG, "Hello world %d\n", 1204);// log().logMessage("test filename6",6, FATAL, "Hello world %d\n", 1205);LOG(DEBUG,"Hello linux\n", 520,1314);LOG(INFO,"Hello linux\n");EnableFILE();LOG(WARNING,"Hello linux\n");LOG(FATAL,"Hello C++", "Today leanring how to write log\n");return 0;
}

此时,日志编写就非常完美的结束了。


感谢阅读!

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

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

相关文章

微信小程序做电子签名功能

文章目录 最近需求要做就记录一下。 人狠话不多&#xff0c;直接上功能&#xff1a; 直接搂代码吧,复制过去就可以用&#xff0c;有其他需求自己改吧改吧。 signature.wxml <!-- 电子签名页面 --> <custom-navbar title"电子签名"show-home"{{fals…

【HarmonyOS】使用AVPlayer播放音乐,导致系统其它应用音乐播放暂停 - 播放音频焦点管理

【HarmonyOS】使用AVPlayer播放音乐&#xff0c;导致系统其它应用音乐播放暂停 - 播放音频焦点管理 一、前言 在鸿蒙系统中&#xff0c;对于音乐播放分为几种场景。音乐&#xff0c;电影&#xff0c;音效&#xff0c;闹钟等。当使用AVPlayer播放音乐时&#xff0c;如果不处理…

Linux中inode、软硬连接

磁盘的空间管理 如何对磁盘空间进行管理&#xff1f; 假设在一块大小为500G的磁盘中&#xff0c;500*1024*1024524288000KB。在磁盘中&#xff0c;扇区是磁盘的基本单位&#xff08;一般大小为512byte&#xff09;&#xff0c;而文件系统访问磁盘的基本单位是4KB&#xff0c;因…

基于卷积神经网络的垃圾分类系统实现(GUI应用)

1.摘要 本文主要实现了一个卷积神经网络模型进行垃圾图像分类&#xff0c;为了提高垃圾分类模型的准确率&#xff0c;使用使用Batch Normalization层、使用早期停止策略来防止过拟合等方法来优化模型&#xff0c;实验结果显示最终优化后的模型准确率较高90%左右。最终&#xf…

IDEA结合GitLab使用

GitLab新建仓库 使用管理员账号创建gitlab仓库创建空白文件填写项目名称及命名空间 注意&#xff1a;取消勾选【使用自述文件初始化仓库】&#xff0c;否则IDEA中push代码报错 设置仓库权限 【设置】-【仓库】-【受保护分支】中需要添加哪些角色可以提交与合并代码&#xff0…

洛谷 P1179 [NOIP2010 普及组] 数字统计 C语言

题目&#xff1a; https://www.luogu.com.cn/problem/P1179 思路&#xff1a;直接暴力过 代码&#xff1a; #include<iostream> using namespace std; int cnt(int x) {int sum 0;while(x){int temp x %10;if(temp 2){sum;}x x/10;}return sum; } int main(void) …

Android APP自学笔记

摘抄于大学期间记录在QQ空间的一篇自学笔记&#xff0c;当前清理空间&#xff0c;本来想直接删除掉的&#xff0c;但是感觉有些舍不得&#xff0c;因此先搬移过来。 Android导入已有外部数据库 2015.06.26在QQ空间记录&#xff1a;在Android中不能直接打开res aw目录中的数据…

在GitHub上fork 别人的仓库 到 自己的仓库,clone到本地,处理后再上传回Github请求PR的过程

如题 一、fork 别人的仓库 到 自己的仓库 这是一种完全的复刻&#xff0c;所有内容都会被拿过来。 点击fork 写信息 创建fork 二、把它clone 到本地 先回到home&#xff0c;打开刚才 我们fork 的工程。 复制地址。 然后 在我们 本地 你去创建一个文件夹 来 接受他 比如我…

MATLAB 识别色块和数量

文章目录 前言步骤 1: 读取图像步骤 2: 转换为 HSV 颜色空间步骤 3: 定义颜色范围步骤 4: 创建颜色掩码步骤 5: 应用形态学操作&#xff08;可选&#xff09;步骤 6: 标记和显示结果完整代码步骤七 返回色块坐标 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&…

深入理解偏向锁、轻量级锁、重量级锁

一、对象结构和锁状态 synchronized关键字是java中的内置锁实现&#xff0c;内置锁实际上就是个任意对象&#xff0c;其内存结构如下图所示 其中&#xff0c;Mark Word字段在64位虚拟机下占64bit长度&#xff0c;其结构如下所示 可以看到Mark Word字段有个很重要的作用就是记录…

文字稿 | MatrixOne2.0.0:AI向量与高可用能力的重磅升级MatrixOne 2.0.0 新特性解读

MatrixOne 2.0.0 是一款 AI 驱动的云原生超融合数据库&#xff0c;采用了存算分离的架构&#xff0c;全面优化了云上资源利用效率。 MatrixOne兼容 MySQL 协议和语法&#xff0c;具备支持混合负载场景的能力&#xff0c;并结合向量数据类型、全文检索等特性&#xff0c;为生成式…

Qt Xlsx安装教程

Qt Xlsx安装教程 安装perl 如果没有安装perl&#xff0c;请参考perl Window安装教程 下载QtXlsxWriter源码 下载地址 ming32-make编译32 lib库 C:\Qt\Qt5.12.12\5.12.12\mingw73_32>d: D:\>cd D:\Code\QtXlsxWriter-master\QtXlsxWriter-master D:\Code\QtXlsxWrit…

【49】AndroidStudio构建其他人开发的Android项目

(1)做Android软件开发&#xff0c;通常会看一些其他人开发的项目源码&#xff0c;当将这些项目的源码通过git clone到本地之后&#xff0c;用AndroidStudio进行打开时&#xff0c;通常会遇到一些环境配置的问题。本文即用来记录在构建他人开发项目源代码这一过程中遇到的一些常…

day08 接口测试(3)——postman工具使用

下载 postman 的历史版本&#xff1a;Postman 历史版本下载 - 简书 我自己根据我的电脑&#xff0c;安装的地址为&#xff1a;https://dl.pstmn.io/download/version/9.31.32/osx_64 今天开始学习 postman 这个测试工具啦。 【没有所谓的运气&#x1f36c;&#xff0c;只有绝…

OpenCV相机标定与3D重建(10)眼标定函数calibrateHandEye()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算手眼标定&#xff1a; g T c _{}^{g}\textrm{T}_c g​Tc​ cv::calibrateHandEye 是 OpenCV 中用于手眼标定的函数。该函数通过已知的机器人…

day08 接口测试(4)知识点完结!!

【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、postman读取外部数据文件&#xff08;参数化&#xff09; 1.1 数据文件简介 1.2 导入外部数据文件 1.2.1 csv文件 1.2.2 导入 json文件 1.3 读取数据文件数据 1.4 案例 1.5 生成测试报告 2、小…

linux 安装 Jenkins 教程

前言 Jenkins 是一个开源的自动化服务器&#xff0c;广泛用于持续集成&#xff08;Continuous Integration&#xff0c;CI&#xff09;和持续交付&#xff08;Continuous Delivery&#xff0c;CD&#xff09;领域。它帮助开发者自动化软件构建、测试、部署等过程&#xff0c;从…

IdentityServer4框架、ASP.NET core Identity

OAuth2.0 IdentityServer4 官网 中文官网 ASP.NET Core Identity提供了一个用来管理和存储用户账户的框架. IdentityServer4是基于ASP.NET Core实现的认证和授权框架&#xff0c;是对OpenID Connect和OAuth 2.0协议的实现。 IdentityServer是一个中间件,它可以添加符合OpenID…

ZZCMS2023存在跨站脚本漏洞(CNVD-2024-44822、CVE-2024-44818)

ZZCMS是一款用于搭建招商网站的CMS系统&#xff0c;由PHP语言开发&#xff0c;可快速搭建&#xff1a;医药招商、保健品招商、化妆品招商、农资招商、孕婴童招商、酒类副食类等招商网站。 国家信息安全漏洞共享平台于2024-11-14公布其存在跨站脚本漏洞。 漏洞编号&#xff1a…

使用Kubernetes部署MySQL+WordPress

目录 前提条件 部署MySQL和WordPress 编写yaml文件 应用yaml文件 存在问题及解决方案 创建PV(持久化卷) 创建一个PVC(持久化卷声明) 部署添加PVC 查看PV对应的主机存储 删除资源 查看资源 删除deployment和service 查看主机数据 删除PVC和PV 删除主机数据 前提条…