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

🍑个人主页: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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[数据集][目标检测]汽车头部尾部检测数据集VOC+YOLO格式5319张3类别

数据集制作单位&#xff1a;未来自主研究中心(FIRC) 版权单位&#xff1a;未来自主研究中心(FIRC) 版权声明&#xff1a;数据集仅仅供个人使用&#xff0c;不得在未授权情况下挂淘宝、咸鱼等交易网站公开售卖,由此引发的法律责任需自行承担 数据集格式&#xff1a;Pascal VOC格…

[数据集][目标检测]烟叶病害检测数据集VOC+YOLO格式612张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;612 标注数量(xml文件个数)&#xff1a;612 标注数量(txt文件个数)&#xff1a;612 标注类别…

如何在 Vue 3 + Element Plus 项目中实现动态设置主题色以及深色模式切换

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、项目依赖和环境配置1. VueUse2. use-element-plus-theme3. 安装依赖 三、实现深色模式切换1. 设置深色模式状态2. 模板中的深色模式切换按钮3. 深色模式的效果展示 四、动态切换主题色五、总结 一、引言 在现代…

C# 比较对象新思路,利用反射技术打造更灵活的比较工具

前言 嘿&#xff0c;大家好&#xff01;如果你之前看过我分享的文章《C# 7个方法比较两个对象是否相等》&#xff0c;你可能会意识到对象比较在实际业务中经常出现的场景。今天&#xff0c;我想继续与大家分享一个在实际项目中遇到的问题。 有一次&#xff0c;我接手了一个别…

Qt多元素控件——QTreeWidget

文章目录 QTreeWidgetQTreeWidget核心方法及信号QTreeWidgetItem核心属性及方法 QTreeWidget使用示例 QTreeWidget QTreeWidget表示树形控件&#xff0c;里面每个元素都是一个QTreeWidgetItem&#xff0c;每个QTreeWidgetItem可以包含多个文本和图标&#xff0c;每个文本/图标…

SEGGERS实时系统embOS推出Linux端模拟器

SEGGER 发布了两个新的 embOS 仿真模拟器&#xff1a;embOS Sim Linux 和 embOS-MPU Sim Linux。 通过模拟 Linux 主机系统上的硬件&#xff0c;取代物理硬件&#xff0c;为开发人员提供了一种无缝的方式来构建原型和测试应用程序。 embOS Sim Linux 端口支持 32 位和 64 位系…

第313题|解积分不等式题目的5种方法常用方法|武忠祥老师每日一题

解题思路&#xff1a;把多阶次积分和函数值联系起来&#xff0c;应该想到泰勒公式。 本题应该使用带有拉格朗日余项的泰勒公式&#xff1a; 方法一&#xff1a; 等式左右两边进行积分&#xff0c;右边第一项常数项不变&#xff0c;第二项&#xff08;x-1/2&#xff09;积完之…

371. 两整数之和

目录 一&#xff1a;题目&#xff1a; 二&#xff1a;代码&#xff1a; 三&#xff1a;结果&#xff1a; 一&#xff1a;题目&#xff1a; 给你两个整数 a 和 b &#xff0c;不使用 运算符 和 - &#xff0c;计算并返回两整数之和。 示例 1&#xff1a; 输入&#xff1a;…

★ C++进阶篇 ★ 多态

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将继续和大家一起学习C进阶篇第一章----多态 ~ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页&#xff1a;椎名澄嵐-CSDN博客 C基础篇专栏&#xff1a;★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 …

Gitea Action 简单配置(CI/CD)

线上pipeline,&#xff08;我使用是本地仓库的&#xff0c;你们使用切换成官网的即可&#xff09; # 工作流的名称name: Build and Push Docker Image deployment-k8s# 触发条件&#xff0c;只在 master 或 main 分支发送推送时触发 on:push:branches:- main# 作业&#xff0c…

深入理解Docke工作原理:UnionFS文件系统详解

在容器技术的世界中&#xff0c;文件系统的设计和实现是其关键组成部分&#xff0c;影响着镜像的构建效率、容器的启动速度以及资源的利用率。**UnionFS&#xff08;联合文件系统&#xff09;**作为Docker的核心文件系统技术&#xff0c;通过其独特的分层结构和写时复制&#x…

mac系统安装最新(截止2024.9.13)Oracle JDK操作记录

文章目录 下载JDK22配置环境变量验证环境变量是否生效整体命令如下 下载JDK22 打开最新版Oracle JDK下载地址 选择想要安装的JDK版本&#xff0c;然后选择适合兼容Mac机器的版本&#xff08;Intel/arm&#xff09;&#xff0c;建议直接下载安装程序&#xff0c;可视化安装 默…

网络安全实训八(y0usef靶机渗透实例)

1 信息收集 1.1 扫描靶机IP 1.2 收集靶机的端口开放情况 1.3 探测靶机网站的目录 1.4 发现可疑网站 1.5 打开可疑网站 2 渗透 2.1 使用BP获取请求 2.2 使用工具403bypasser.py探测可疑网页 2.3 显示可以添加头信息X-Forwarded-For:localhost来访问 2.4 添加之后转发&#xff…

Java入门,初识Java

Java背景知识 Java是早期美国 sun 公司&#xff08;Stanford University Network&#xff09;在1995年推出的一门计算机高级编程语言。Java早期称为Oak&#xff08;中文翻译为&#xff1a;橡树&#xff09;&#xff0c;后期改名为Java。&#xff08;因为当时sun公司门口有很多…

使用ENVI之辐射定标

将下载好的遥感影像导入遥感影像处理软件ENVI 5.6中 使用ENVI 5.6的Toolbox中的Radiometric Calibration工具 跳出的Date Input File界面中选中要进行辐射定标的文件选中 再在跳出的Radiometric Calibration界面中将Output Interleave改为BIL再点击Apply FLAASH Settings Soale…

初始爬虫7

针对数据提取的项目实战&#xff1a; 补充初始爬虫6的一个知识点&#xff1a; etree.tostring能够自动补全html缺失的标签&#xff0c;显示原始的HTML结构 # -*- coding: utf-8 -*- from lxml import etreetext <div> <ul> <li class"item-1">…

基于Boost库的搜索引擎开发实践

目录 1.项目相关背景2.宏观原理3.相关技术栈和环境4.正排、倒排索引原理5.去标签和数据清洗模块parser5.1.认识标签5.2.准备数据源5.3.编写数据清洗代码parser5.3.1.编写读取文件Readfile5.3.2.编写分析文件Anafile5.3.2.编写保存清洗后数据SaveHtml5.3.2.测试parser 6.编写索引…

STM32内部闪存FLASH(内部ROM)、IAP

1 FLASH简介 1 利用程序存储器的剩余空间来保存掉电不丢失的用户数据 2 通过在程序中编程(IAP)实现程序的自我更新 &#xff08;OTA&#xff09; 3在线编程&#xff08;ICP把整个程序都更新掉&#xff09; 1 系统的Bootloader写死了&#xff0c;只能用串口下载到指定的位置&a…

从基础到进阶:利用EasyCVR安防视频汇聚平台实现高效视频监控系统的五步走

随着科技的飞速发展&#xff0c;视频监控技术在社会安全、企业管理、智慧城市构建等领域扮演着越来越重要的角色。一个高效智能的视频监控管理系统不仅能够提升监控效率&#xff0c;还能在预防犯罪、事故预警、数据分析等方面发挥巨大作用。 一、需求分析 在设计视频监控管理…

sql中索引查看是否生效

在pg数据库中有多种索引存在&#xff0c;在一般情况下我们取使用普通索引 以下是一些常见导致索引未命中的原因和优化策略 1.如果查询中的条件与索引字段的顺序不匹配&#xff0c;或者索引字段没有完全包含在查询条件中&#xff0c;索引可能不会被使用。 2.在查询中使用函数…