深度揭秘:日志打印的艺术与实战技巧,让你的代码会说话!

🍑个人主页:Jupiter.
🚀 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

  • `🍁日志`
    • `🍂日志分模块实现讲解`
      • `🍃日志等级的实现`
      • `🥥日志时间`
            • *时间的获取*
      • `🌈文件名与行号的获取`
      • `📚日志内容`
            • `vsnprintf函数`
    • `🌾日志打印的优化处理`
      • `🍁将日志打印函数变为宏函数`
          • `C语言宏的可变参数`
      • `📕将日志内容保存到文件中`
    • `🚀日志整体代码实现`


🍁日志

🍂日志分模块实现讲解

  • 日志一般需要一下的内容:日志等级,日志打印的时间,日志打印所在的文件名,日志打印的所在代码行号,日志内容

🍃日志等级的实现

将日志等级枚举出来,然后将用户传入的等级转为字符串即可。

代码实现:

//日志等级枚举
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};//将日志等级转为字符串
string LevelToString(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 "UNKNOW";}
}

🥥日志时间

时间的获取

time函数

  • 功能:获取一个时间戳
  • 返回值:time_t类型
  • 参数:一般设置为nullptr

localtime_r函数

  • 功能:可以将一个时间戳转化为年月日时分秒。
  • 参数:
    • 参数一:传入一个time_t的指针(也就是调用time函数的返回值的地址)。
    • 参数二:一个struct tm类型的结构体,里面包含的字段如下:

代码实现:

//将时间转为字符串
string timeToString(struct tm *stm){char timebuffer[64];snprintf(timebuffer, sizeof(timebuffer), "%d-%d-%d %d:%d:%d",stm->tm_year + 1900, stm->tm_mon + 1, stm->tm_mday,stm->tm_hour, stm->tm_min, stm->tm_sec);return timebuffer;
}//时间
time_t curtime = time(nullptr);
struct tm stm;
localtime_r(&curtime, &stm);
string timestr = timeToString(&stm);

🌈文件名与行号的获取

使用预处理器宏__FILE__和__LINE__)来获取当前文件的名称和行号。这些宏在编译时由预处理器替换为相应的文件名和行号字符串

代码示例:

#include <stdio.h>  void printLocation() {  printf("File: %s, Line: %d\n", __FILE__, __LINE__);  
}  int main() {  printLocation(); // 输出当前文件名和行号  return 0;  
}

📚日志内容

  • 利用可变参数获取

提取可变参数的内容的示例:

void test(int num, ...)
{va_list args;va_start(args, num);while (num--){int data = va_arg(args, int);cout << data << endl;}va_end(args);
}test(3,10,20,30);

根据上面的例子对可变参数原理进行简单介绍:

  • test函数在调用的时候,会进行传参,传参的时候都是从右向左进行实例化的,在实例化的时候就会从右向左一次入栈,最右侧的固定参数(也就是离可变参数最近的一个参数),即int num会在离可变部分最近的位置,上面的函数是就是要将10,20,30依次提取出来。
  • 函数内部就是:定义了一个指针,args,实际上va_list类型是void*,然后利用num参数和va_start宏将args指针初始化,实际上是args = &num-sizeof(num);这样args就指向了可变参数的第一个参数,然后利用va_arg宏将第一个可变参数提取出来,实际是int data = *((int *)args);然后会自动在数字上
    加上sizeof(int),即args-=sizeof(int);就这样依次提取出来,最后利用va_end将args置nullptr

图解:

像上面示例那样提取,太麻烦了,下面介绍一个函数:

vsnprintf函数
  • 函数介绍:
  • 功能:可以将可变参数部分按照指定的格式,提取出来放到指定的字符串中(或则指定大小的字符串中)

利用vsnprintf函数是日志内容的获取:

代码实现:

 //日志内容,多参数void log(int level, string filename, int line, const char *format, ...){//......//日志内容,多参数的实现va_list args;  va_start(args, format);char ContentStr[1024];vsnprintf(ContentStr, sizeof(ContentStr), format, args);va_end(args);//......}

🌾日志打印的优化处理

  • 因为每次打印日志的时候,都会自己传入行号与所在文件的文件名,比较麻烦;

🍁将日志打印函数变为宏函数

C语言宏的可变参数

如果你需要定义一个包含多条语句的多参数宏,你可以使用\来连接多行,或者更常见的是,使用do { … } while (0)结构来包围宏体。这种方式有助于避免在使用宏时可能出现的语义错误。

当你定义一个可变参数宏时,宏的参数列表中的最后一个参数必须是省略号...,它表示宏可以接受任意数量的附加参数。在宏体内部,__VA_ARGS__是一个特殊的标识符,它会被替换为宏调用时传递给宏的所有附加参数(如果有的话)。##__VA_ARGS__作用是,让可变参数部分可以不传入参数。

代码实现:

// LOG(DEBUG, " Content is :%s %d %f ", "helloworld", 10, 3.14);
#define LOG(level, format, ...)                                \ do                                                         \{                                                          \log(level, __FILE__, __LINE__, format, ##__VA_ARGS__); \} while (0)

📕将日志内容保存到文件中

代码实现:

//将日志存放到文件中,利用的C++的文件读写操作
void IsSaveFile(string &message)
{// 创建一个ofstream对象,与文件"example.txt"关联  // 如果文件不存在,会自动创建;如果文件已存在,会被覆盖 ofstream out(FILENAME, ios::app);  // 检查文件是否成功打开 if (!out){return;}// 向文件写入内容out << message << endl;//关闭文件out.close();
}
//将日志放到一个string里面
string message = "[ " + levelstr + " ]  [ " + timestr + " ]  [ "+ filename + " ]  [ " + to_string(line) + " ]  [ " + ContentStr + " ]" + "\0";

🚀日志整体代码实现

Log.hpp

#pragma#include <iostream>
#include <string>
#include <ctime>
#include <cstdarg>
#include <unistd.h>
#include <fstream>using namespace std;#define FILENAME "LOG.txt"   //保存LOG的文件名bool IsSave = false;        //标记是否需要保存到文件// LOG(DEBUG, " Content is :%s %d %f ", "helloworld", 10, 3.14);
#define LOG(level, format, ...)                                \ do                                                         \{                                                          \log(level, __FILE__, __LINE__, format, ##__VA_ARGS__); \} while (0)//两各接口,修改IsSave的值,便于外部修改
#define EnIsSave()       \   do                 \{                  \IsSave = true; \} while(0)#define EnIsPrint()      \do                 \{                  \IsSave = false; \} while(0)//日志等级枚举
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};//将日志等级转为字符串
string LevelToString(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 "UNKNOW";}
}//将时间转为字符串
string timeToString(struct tm *stm)
{char timebuffer[64];snprintf(timebuffer, sizeof(timebuffer), "%d-%d-%d %d:%d:%d",stm->tm_year + 1900, stm->tm_mon + 1, stm->tm_mday,stm->tm_hour, stm->tm_min, stm->tm_sec);return timebuffer;
}//如果是多线程打印日志,打印到显示器,显示器是公共资源(临界资源),需要保护
pthread_mutex_t mutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;//将日志存放到文件中,利用的C++的文件读写操作
void IsSaveFile(string &message)
{ofstream out(FILENAME, ios::app);  if (!out){return;}out << message << endl;out.close();
}//  日志的等级 时间 文件 行号 日志内容
void log(int level, string filename, int line, const char *format, ...)
{//等级string levelstr = LevelToString(level);//时间time_t curtime = time(nullptr);struct tm stm;localtime_r(&curtime, &stm);string timestr = timeToString(&stm);//日志内容,多参数va_list args;va_start(args, format);char ContentStr[1024];vsnprintf(ContentStr, sizeof(ContentStr), format, args);va_end(args);//将日志放到一个string里面string message = "[ " + levelstr + " ]  [ " + timestr + " ]  [ " + filename + " ]  [ " + to_string(line) + " ]  [ " + ContentStr + " ]" + "\0";//保护临界资源//加锁pthread_mutex_lock(&mutex);if (!IsSave){cout << "[ "<< levelstr << " ]  [ "<< timestr << " ]  [ "<< filename << " ]  [ "<< line << " ]  [ "<< ContentStr << " ]"<< endl;}else{IsSaveFile(message);}//释放锁pthread_mutex_unlock(&mutex);
}

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

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

相关文章

web基础之文件上传

1.下载安装 下载地址 链接&#xff1a;百度网盘-链接不存在 提取码&#xff1a;jhks 安装 直接把他放在phpstudy的WWW目录中。&#xff08;phpstudy的下载安装&#xff0c;可以自行百度一下&#xff09; 打开 访问地址&#xff1a;127.0.0.1/upload-labs 问题 这里可能…

开源PDF工具 Apache PDFBox 认识及使用(知识点+案例)

文章目录 一、认识PDFBox一、pandas是什么&#xff1f;二、导入依赖三、基础功能demo1&#xff1a;读取pdf所有内容demo2&#xff1a;读取所有页内容&#xff08;分页&#xff09;demo3&#xff1a;添加页眉、页脚demo4&#xff1a;添加居中45文字水印demo5&#xff1a;添加图片…

数据结构——栈和队列(队列的定义、顺序队列以及链式队列的基本操作)

目录 队列&#xff08;queue&#xff09;的定义 顺序队——队列的顺序表示和实现 顺序队列&#xff08;循环队列&#xff09;的类型定义 顺序队列上溢问题的解决方法 ​编辑 循环队列的基本操作 队列的基本操作——队列的初始化 队列的基本操作——求队列的长度 队列的…

Element UI入门笔记(个人向)

Element UI入门笔记 将页面分割为一级菜单、二级菜单、导航栏三个部分&#xff1b;使用npm下载安装&#xff0c;使用语句npm i element-ui -s; 布局组件 el-form 用于创建和管理表单&#xff1b;从属性上看&#xff1a; :model&#xff1a;用于双向数据绑定&#xff0c;将表单…

Windows下SDL2创建最简单的一个窗口

先看运行效果 再上代码&#xff1a; #include <stdio.h> #include "SDL.h"int main(int argc, char* argv[]) {// 初始化SDL视频子系统if (SDL_Init(SDL_INIT_VIDEO) -1){printf("Error: %s\n", SDL_GetError());return -1;} // 创建一个窗口SDL_…

『功能项目』战士职业平A怪物掉血【44】

我们打开上一篇43事件中心的项目&#xff0c; 本章要做的事情是给主角增加一个xxxCtrl.cs脚本&#xff0c;再创建一个xxxOpt.cs调用xxxCtrl.cs机制层利用事件中心再写一个主角战士平A对怪物的伤害 首先创建脚本&#xff1a;PlayerCtrl.cs using UnityEngine; public class Pla…

JavaScript 笔记汇总

JavaScript 笔记汇总 引入方式 内部方式 通过 script 标签包裹 JavaScript 代码。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>JavaScript 基础 - 引入方式</title> </head> <…

校园社团|基于springBoot的校园社团信息管理系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书&#xff08;可指定任意题目&#xff09; 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信…

顺序表(c语言实现)

顺序表是一种数据结构&#xff0c;它在计算机内存中以连续的存储位置来存储数据元素。 一、特点 1. 随机访问&#xff1a;可以在常数时间内访问特定位置的元素&#xff0c;例如&#xff0c;通过下标可以快速找到对应元素。 2. 存储密度高&#xff1a;不需要额外的指针来链接…

【运维监控】Prometheus+grafana+kafka_exporter监控kafka运行情况

运维监控系列文章入口&#xff1a;【运维监控】系列文章汇总索引 文章目录 一、prometheus二、grafana三、部署kafka_exporter1、下载2、解压3、配置4、启动5、验证 四、prometheus集成grafana监控kafka1、修改prometheus配置2、导入grafana模板3、验证 本示例通过kafka_export…

从0书写一个softmax分类 李沐pytorch实战

输出维度 在softmax 分类中 我们输出与类别一样多。 数据集有10个类别&#xff0c;所以网络输出维度为10。 初始化权重和偏置 torch.norma 生成一个均值为 0&#xff0c;标准差为0.01,一个形状为size(num_inputs, num_outputs)的张量偏置生成一个num_outputs 10 的一维张量&a…

【计网】数据链路层:概述之位置|地位|链路|数据链路|帧

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山岗&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ 1. 在OSI体系结构中的位置 1. 位置&#xff1a;数…

ICMP

目录 1. 帧格式2. ICMPv4消息类型(Type = 0,Code = 0)回送应答 /(Type = 8,Code = 0)回送请求(Type = 3)目标不可达(Type = 5)重定向(Type = 11)ICMP超时(Type = 12)参数3. ICMPv6消息类型回见TCP/IP 对ICMP协议作介绍 ICMP(Internet Control Message Protocol…

即插即用!高德西交的PriorDrive:统一的矢量先验地图编码,辅助无图自动驾驶

Driving with Prior Maps: Unified Vector Prior Encoding for Autonomous Vehicle Mapping 论文主页&#xff1a;https://misstl.github.io/PriorDrive.github.io/ 论文链接&#xff1a;https://arxiv.org/pdf/2409.05352 代码链接&#xff1a;https://github.com/missTL/Pr…

基于python+django+vue的学生成绩管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于协同过滤pythondjangovue…

用Python解决综合评价问题_模糊综合评价,决策树与灰色关联分析

一&#xff1a;模糊综合评价 模糊综合评价是一种有效的处理不确定性和模糊性的评价方法&#xff0c;特别是在人才评价等领域。它允许我们综合考虑多个评价指标&#xff0c;并给出一个综合的评价结果。以下是利用模糊综合评价对人才进行评价的步骤&#xff1a; 确定评价指标&am…

Git常用指令整理【新手入门级】【by慕羽】

Git 是一个分布式版本控制系统&#xff0c;主要用于跟踪和管理源代码的更改。它允许多名开发者协作&#xff0c;同时提供了强大的功能来管理项目的历史记录和不同版本。本文主要记录和整理&#xff0c;个人理解的Git相关的一些指令和用法 文章目录 一、git安装 & 创建git仓…

【AI大模型】ChatGPT模型原理介绍(上)

目录 &#x1f354; 什么是ChatGPT&#xff1f; &#x1f354; GPT-1介绍 2.1 GPT-1模型架构 2.2 GPT-1训练过程 2.2.1 无监督的预训练语言模型 2.2.2 有监督的下游任务fine-tunning 2.2.3 整体训练过程架构图 2.3 GPT-1数据集 2.4 GPT-1模型的特点 2.5 GPT-1模型总结…

深度学习-神经网络

文章目录 一、基本组成单元&#xff1a;神经元二、神经网络层三、偏置与权重四、激活函数1.激活函数的作用2.常见的激活函数1).Sigmoid2).Tanh函数3).ReLU函数 五、优点与缺点六、总结 神经网络&#xff08;Neural Network, NN&#xff09;是一种模拟人类大脑工作方式的计算模型…

Debian11.9镜像基于jre1.8的Dockerfile

Debian11.9基于jre1.8的Dockerfile编写 # 使用Debian 11.9作为基础镜像 FROM debian:11.9 # 维护者信息&#xff08;建议使用LABEL而不是MAINTAINER&#xff0c;因为MAINTAINER已被弃用&#xff09; LABEL maintainer"caibingsen" # 创建一个目录来存放jre …