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

【高并发服务器】六、日志宏的实现

文章目录

  • 日志宏封装

在这里插入图片描述

日志宏封装

​ 目的就是为了实现一些宏函数,让它们辅助我们进行日志信息的打印,我们想要的格式像下面这样子:

[2023/6/21 21:28:30 main.c:28] 文件打开失败

​ 也就是这样子:

[时间 出现错误的文件:行号] 错误信息

​ 对于出现错误的文件和行号来说其实不难,因为 C 语言本身就给我们提供了对应的宏,分别是 __FILE____LINE__

​ 而对于时间来说,这得用几个函数来帮忙:

// 获取系统时间戳
time_t time(NULL); // 通过系统时间戳参数来获取本地时间的结构体tm
struct tm* localtime(time_t* t); // 将结构体tm通过format形式根据max大小存放到buf中去
char* strftime(char* buf, int max, char* format, struct tm* tm); // 通过format格式将可变参数写到文件指针所指的文件中去
int fprintf(FILE* fp, char* format, ...); 

​ 我们的日志宏和服务器头文件 server.hpp 放在一起。因为我们要用到宏,那么就得用 <cstdio> 头文件,所以要将其包含进来。

​ 接着我们写一个简单宏定义:

#ifndef __MY_LOG_H__
#define __MY_LOG_H__
#include <cstdio>#define LOG(format, ...) fprintf(stdout, "[%s:%d] " format, __FILE__, __LINE__, __VA_ARGS__)#endif

​ 这里的 LOG 就是一个日志宏,它的第一个参数是一个格式化字符串 format,用于指定输出信息的格式,注意 format 只是我们在宏定义常常起的参数名,并不是一个关键字或预定义标识符

LOG 的第二个参数 ... 是 可变参数列表的一种表示,而 fprintf 函数中的 __VA_ARGS__C 语言中的一个预处理器宏,也是用于表示一个可变参数列表,当我们调用 LOG 宏的时候,... 中的多个可变参数都会在预处理阶段传递给 __VA_ARGS__

​ 比如说下面的例子:

LOG("%s: %d", "liren", 10);
最后在预处理阶段会被替换为如下形式:
fprintf(stdout, "[%s:%d] %s: %d", __FILE, __LINE, "liren", 10);

​ 至于具体 __VA_ARGS__ ... 的区别可以看下面这段理解!

__VA_ARGS__ ... 的区别:

​ 它们用来表示可变参数列表的语法元素,但它们的使用方式和作用范围有所不同。

... 是 C99 标准引入的 语法,用于表示函数或宏定义中的可变参数列表。在函数定义或宏定义中,... 必须放在参数列表的最后一个位置,用来表示后面还有一些可变数量的参数。例如,下面是一个使用 ... 表示可变参数的函数定义:

void my_printf(const char* format, ...);

​ 在函数调用时,可以使用类似于 printf 函数的方式传递可变数量的参数,例如:

my_printf("The value of x is %d\n", x);
my_printf("Hello, %s!\n", name);

​ 在这种情况下,编译器会将可变参数列表转换为一个类型为 va_list 的对象,然后可以使用 stdarg.h 中定义的函数和宏如 va_start()vsnprintf() 等来访问和处理这些参数。

__VA_ARGS__ 则是一个预处理器宏,用于表示宏定义中的可变参数列表。在宏定义中,__VA_ARGS__ 可以出现在参数列表的任意位置,用来表示可变数量的参数。例如,下面是一个使用 __VA_ARGS__ 表示可变参数的宏定义:

#define LOG(format, ...) printf(format, __VA_ARGS__)

​ 在这种情况下,预处理器会将 __VA_ARGS__ 展开为一系列逗号分隔的参数,然后将它们传递给宏定义中的 printf 函数进行输出。但是一般防止不传可变列表参数报错,我们会 __VA_ARGS__ 前面加上 ## 表示展开后的可变参数列表,如下所示:

#define LOG(format, ...) printf(format, ##__VA_ARGS__)

​ 并且 上面的 printf 中使用的时候是不能用 ... 的,只能使用 __VA_ARGS__ 来表示接收到的可变参数列表

​ 总的来说,...__VA_ARGS__ 都是用来表示可变参数列表的语法元素,但是 前者用于函数定义和函数调用中后者只能用于宏定义中。它们的作用和使用方式有所不同,但都可以方便地处理可变数量的参数。

​ 接下来我们再来加入时间等信息,让宏日志更完善一点!一般我们如果想要在宏定义的时候写多行代码,都会使用 do while(0) 语句来配合,涉及到换行的话要使用反斜杠 \ 在语句最后面,下面给出结合打印时间的代码的完善日志宏:

#include <cstdio>
#include <time.h>#define LOG(format, ...) do{\char timebuffer[128];\time_t timestamp = time(NULL);\struct tm* timeinfo = localtime(&timestamp);\strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", timeinfo);\fprintf(stdout, "[%s %s:%d] " format, timebuffer, __FILE__, __LINE__, __VA_ARGS__);\
}while(0)

​ 调用的结果如下:

LOG("%s-%d\n", "liren", 100);
结果:
[2023-06-21 23:17:03 gobang.cc:5] liren-100

​ 这样子就结束了吗❓❓❓

​ 当然不是,因为还有 bug,因为如果我们 使用 LOG 宏的时候不传可变参数的话,那么预处理时候就会报错,如下所示:

LOG("liren");
编译时候会报错:[liren@VM-8-7-centos source]$ make
g++ -ogobang gobang.cc logger.hpp 
In file included from gobang.cc:1:0:
gobang.cc: In function ‘int main():
logger.hpp:11:86: error: expected primary-expression before ‘)’ tokenintf(stdout, "[%s %s:%d] " format, timebuffer, __FILE__, __LINE__, __VA_ARGS__);\^
gobang.cc:5:5: note: in expansion of macro ‘LOG’LOG("liren");^~~
make: *** [makefile:2: gobang] Error 1

​ 解决这个问题很简单,只需要使用 ##__VA_ARGS__ 来表示展开后的参数列表。这个语法中的 ## 表示将 __VA_ARGS__ 前面的逗号去掉,避免在展开后出现语法错误!需要注意的是,## 的使用在不同的编译器和平台上可能有所不同。在使用 ## 时需要注意平台兼容性和语法规则。

​ 所以修改完代码如下:

#include <cstdio>
#include <time.h>#define LOG(format, ...) do{\char timebuffer[128] = {0};\time_t timestamp = time(NULL);\struct tm* timeinfo = localtime(&timestamp);\strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", timeinfo);\fprintf(stdout, "[%s %s:%d] " format "\n", timebuffer, __FILE__, __LINE__, ##__VA_ARGS__);\
}while(0)

​ 这样子就结束了吗❓❓❓还是没结束,因为我们到时候项目中会打印很多日志,如果我们不对日志分等级的话,那么可能会导致日志比较乱,下面我们定义一些等级的宏:

#define INF 0    // 提示型等级
#define DEBUG 1  // 调试型等级
#define ERROR 2  // 错误型等级
#define DEFAULT_LOG_LEVEL DEBUG  // 默认的日志等级

​ 然后我们再将这些等级宏和我们刚才写的日志宏封装起来:

#define ILOG(format, ...) LOG(INF, format, ##__VA_ARGS__)
#define DLOG(format, ...) LOG(DEBUG, format, ##__VA_ARGS__)
#define ELOG(format, ...) LOG(ERROR, format, ##__VA_ARGS__)

​ 此时看到 LOG 宏的第一个参数传入的是对应的等级,那我们想要去改一下原来的日志宏的参数,多加一个参数 level,并且我们判断一下当前的等级也就是 DEFAULT_LOG_LEVEL 是否小于传入进来的等级,是的话我们就不需要去打印,因为我们此时程序说明不需要上升到这种级别的日志打印!

#define LOG(level, format, ...) do{\if(DEFAULT_LOG_LEVEL > level) break\char timebuffer[128] = {0};\time_t timestamp = time(NULL);\struct tm* timeinfo = localtime(&timestamp);\strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", timeinfo);\fprintf(stdout, "[%s %s:%d] " format "\n", timebuffer, __FILE__, __LINE__, ##__VA_ARGS__);\
}while(0)

​ 所以完整的代码是这样子的:

#include <cstdio>
#include <time.h>#define INF 0    // 提示型等级
#define DEBUG 1  // 调试型等级
#define ERROR 2  // 错误型等级
#define DEFAULT_LOG_LEVEL DEBUG  // 默认的日志等级#define LOG(level, format, ...) do{\if(DEFAULT_LOG_LEVEL < level) break;\char timebuffer[128] = {0};\time_t timestamp = time(NULL);\struct tm* timeinfo = localtime(&timestamp);\strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", timeinfo);\fprintf(stdout, "[%s %s:%d] " format "\n", timebuffer, __FILE__, __LINE__, ##__VA_ARGS__);\
}while(0)// 将等级和日志打印封装起来
#define ILOG(format, ...) LOG(INF, format, ##__VA_ARGS__)
#define DLOG(format, ...) LOG(DEBUG, format, ##__VA_ARGS__)
#define ELOG(format, ...) LOG(ERROR, format, ##__VA_ARGS__)

​ 下面我们测试一下:

ILOG("this is INF");
DLOG("this is DEBUG");
ELOG("this is ERROR");// 运行结果:
[liren@VM-8-7-centos source]$ ./gobang 
[2023-06-21 23:41:55 gobang.cc:5] this is INF
[2023-06-21 23:41:55 gobang.cc:6] this is DEBUG

在这里插入图片描述

http://www.dtcms.com/a/499041.html

相关文章:

  • 什么是网络割接
  • 中山网站建设文化报价个人网站设计结构图
  • 专业简历制作网站推荐渭南网站建设网站排名优化
  • Electron学习(一):创建第一个应用并打包成功
  • EF Core FromExpression 方法
  • 工厂方法模式
  • 做装机u盘那个网站好市桥做网站
  • SAP MM采购对账功能分享
  • 网页设计与网站建设考试名词解释2019网站建设工作的作用
  • 【有源码】基于Python与Spark的火锅店数据可视化分析系统-基于机器学习的火锅店综合竞争力评估与可视化分析-基于用户画像聚类的火锅店市场细分与可视化研究
  • Linux: perf: sched latency,周期性抓取看趋势,做对比
  • 统计学重要思想
  • Android 本地存储方案深度解析:SharedPreferences、DataStore、MMKV 全面对比
  • 网站开发前后端中山做外贸网站
  • ElastiCache Redis 内存告警深度分析与运维实战指南
  • Spring5.3.10源码编译和调试(IDEA+Gradle)的过程
  • JS | 知识点总结 - 原型链
  • 【Docker】Docker镜像仓库
  • EEException: Geometry.area: Unable to perform this geometry operation.
  • 逻辑和共情
  • linux安装输入法
  • git连接远程仓库并拉去推送以及克隆命令
  • steam新品节游戏推荐!手机怎么玩steam游戏!
  • OpenHarmony Stage模型深度解剖:从Ability Kit到沙箱隔离的全链路底层原理
  • 基于 GEE 的 MODIS 昼夜地表温度数据可视化与导出全流程解决方案
  • 【Docker】记录一次使用docker部署dify网段冲突的问题
  • 缓存三剑客问题
  • 构建AI智能体:六十七、超参数如何影响大模型?通俗讲解原理、作用与实战示例
  • timm教程翻译:(一)Overview
  • 手写Spring第5弹:告别项目混乱:用Maven构建标准Java项目目录结构