当前位置: 首页 > news >正文

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

🍑个人主页: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.mrgr.cn/news/28581.html

相关文章:

  • 搜维尔科技:Haption力触觉交互,虚拟机械装配验证
  • 《物理学报》
  • python文件命名,不注意容易出错
  • Linux kernel 堆溢出利用方法(二)
  • 719. 找出第 K 小的数对距离
  • 鸿蒙系统(HarmonyOS)介绍
  • Vscode 中新手小白使用 Open With Live Server 的坑
  • Java之线程篇四
  • 基于python+django+vue的外卖管理系统
  • 进程之信号
  • MySQL如何某种类统计数据,没有记录种类的自动补充0
  • 近期值得关注的扩散模型Diffusion与时间序列结合的文章
  • 常见经典递归过程解析
  • 嵌入式系统中的u-boot、kernel、rootfs的区别与关系
  • 【20.5 python中的FastAPI】
  • bootstrapping in the main distro: listing WSL distros: running WSL xxxx
  • Python酷库之旅-第三方库Pandas(120)
  • Java基础-反射
  • MATLAB系列06:复数数据、字符数据和附加画图类
  • Linux: fs:支持最大的文件大小 limit file;truncate
  • 操作数组不越界的妙法C++
  • Nginx:高性能Web服务器与反向代理的深度剖析
  • rk3568 Android12 增加 USB HOST 模式开关(二)
  • Java 技巧 如何在IDEA2024 中快速打出System.out.println();
  • ICMP
  • 数据与结构算法平衡二叉树详解叉树--基本概念