惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
C
Cisco Blogs
博客园 - 叶小钗
P
Privacy International News Feed
Jina AI
Jina AI
Apple Machine Learning Research
Apple Machine Learning Research
T
Threatpost
IT之家
IT之家
博客园 - 聂微东
Know Your Adversary
Know Your Adversary
Help Net Security
Help Net Security
罗磊的独立博客
I
Intezer
S
Schneier on Security
博客园_首页
C
CERT Recently Published Vulnerability Notes
雷峰网
雷峰网
Cisco Talos Blog
Cisco Talos Blog
宝玉的分享
宝玉的分享
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Webroot Blog
Webroot Blog
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
P
Privacy & Cybersecurity Law Blog
T
The Exploit Database - CXSecurity.com
PCI Perspectives
PCI Perspectives
Security Latest
Security Latest
H
Heimdal Security Blog
S
Secure Thoughts
Hacker News: Ask HN
Hacker News: Ask HN
Y
Y Combinator Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Microsoft Security Blog
Microsoft Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
SecWiki News
SecWiki News
The GitHub Blog
The GitHub Blog
A
Arctic Wolf
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
T
Threat Research - Cisco Blogs
Engineering at Meta
Engineering at Meta
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

开源实验室

一个月,纯VibeCoding,全平台云笔记APP|开源实验室 kymjs.com 借助 API 手写一个 Transformer 架构|开源实验室 PT 站规则扫盲|开源实验室 使用 RAGFlow 搭建一套 AI 客服知识库|开源实验室 终于搞定了,本地部署 RAGFlow|开源实验室 阿富汗-中亚最倒霉国家|开源实验室 【HarmonyOS】TheRouter 鸿蒙版新手入门|开源实验室 可能是 AGP8 编译最快的方案|开源实验室 NAS - 玩点有意思的|开源实验室 动态路由 TheRouter 的设计与实践|开源实验室 没错,TheRouter 是我写的|开源实验室 货拉拉 Android 模块化路由框架:TheRouter|开源实验室 菲律宾这国家到底怎么回事|开源实验室 印度的穆斯林王朝——德里苏丹|开源实验室 学历史有什么用|开源实验室 印度的列国时代大一统——孔雀王朝|开源实验室 印度列国时代的百家争鸣——沙门思潮|开源实验室 印度真是个神奇的国度——总章|开源实验室 某一天,中国真会超过美国吗?|开源实验室 基于 C++ 的 Android 协程设计|开源实验室 Gradle 6.X 上传 aar 到 Nexus 私服|开源实验室 扁平化管理,就是管理者的失职|开源实验室 再聊 Git Flow|开源实验室 Flutter 设置控件是否可见|开源实验室 Flutter 线性布局:Column 和 Row|开源实验室 开源一段 Mac 批量压缩图片的脚本|开源实验室 Leader 的自我修养——学会预测|开源实验室 玩玩区块链——概念|开源实验室 一条电商 Android 工程化实践|开源实验室
自定义C/C++日志输出函数|开源实验室
2020-08-07 · via 开源实验室

对本文有任何问题,可加我的个人微信询问:kymjs666

在繁杂的项目中,日志打印必不可少。但是编写打印的工作,有时候是无趣的、繁琐的、浪费精力的。
如何能够快速、方便的编写打印;如何清晰、准确的定位;如何简单并优雅的实现;最后才能让我们摆脱这样枯燥的、重复的工作?
网上有很多强大的日志类工具,我也都使用过一些,有时候也并没有理想中的方便。今天我想分享给大家的一套我自己的解决方案。

C++ 中标准输出方式

对于单个变量输出,可以如下方式:

int delay = 5;
std::cout << "delay:" << delay << std::endl;

对于多变量信息输出则需要如下方式:

#include <iostream>
using namespace std;

int main(int argc, char **argv)
{
    char *Name = "Bill";
    int Age = 10;
    float Score = 86.5;
    float Height = 121.3;
    float Body_Weight = 36;

    std::cout << "Name:" << Name << ", "
              << "Age:" << Age << ", "
              << "Score:" << Score << ", "
              << "Height:" << Height << ", "
              << "Body_Weight:" << Body_Weight << std::endl;
}

问题的提出

大家可以看见,需要添加很多对应变量名的字符串,导致写一次打印,非常耗时间。
我一直在想有没有更好的解决方案,形如 logOut(a, b, c, d); 这样简单方便的输出方式?
最后经过不断探索终于找到了一份这样的解决方案,而且只需要加入一个头文件即可,代码如下:

#include "logger.h"

int main(int argc, char **argv)
{
    char *Name = "Bill";
    int Age = 10;
    float Score = 86.5;
    float Height = 121.3;
    float Body_Weight = 36;

    logDebug(Name, Age, Score, Height, Body_Weight);
}

输出结果:

DEBUG [..\cppdemo\main.cpp@main#11]Name:Bill, Age:10, Score:86.5, Height:121.3, Body_Weight:36

大家可能注意到了,背景图的内容就是这个,还有Info、Warn、Error并对应不同颜色输出。

如何实现

那么对于以上效果,如何才能实现呢?
下面我将带领大家一步一步讲解我的心路历程与解决方案。这样也容易让大家了理解其中原理。

单个变量的实现

究其根本,就是减少对变量名称字符串的输入,但是打印的时候能够对应显示,便于我们分析。
对于单个变量,我很早就想到,用过宏“#”就可以轻松实现。

宏 “#” 的妙用

如果熟悉c++宏的小伙伴,可能知道里面有个“#”的用法,可以将对应的参数变成字符串,效果如下:

#define  toStr(x)  #x

char *str = toStr(hello);  
//  等价于 
char *str = "hello";

那么对于单个变量最简单的宏实现方式就是:

#define logs(x)  std::cout << #x":" << x << std::endl;

...

int delay = 5;
int other = 3;

logs(delay);
logs(other);

输出结果:

显示文件名、函数、行号

对于想要显示文件位置信息,c++中也有对应的宏,分别是 __FILE____FUNCTION____LINE__
只需要做如下改动即可。

#define FILE_INFO   "[" << __FILE__ << '@' << __FUNCTION__ << '#' << __LINE__  << "]"
#define logs(x)     std::cout << FILE_INFO << #x":" << x << std::endl

...

int delay = 5;
int other = 3;

logs(delay);
logs(other);

输出结果:

[..\cppdemo\main.cpp@main#21]delay:5
[..\cppdemo\main.cpp@main#22]other:3

设置打印颜色

大家都知道在Linux使用 ls 命令列出文件列表时,不同的文件类型会用不同的颜色显示。那么如何实现这样带颜色的文本输出呢?
在bash中,通常我们可以使用echo命令加-e选项输出各种颜色的文本,例如:

echo -e "\033[31mRed Text\033[0m"
echo -e "\033[32mGreen Text\033[0m"
echo -e "\033[33mYellow Text\033[0m"
echo -e "\033[34mBlue Text\033[0m"
echo -e "\033[35mMagenta Text\033[0m"
echo -e "\033[36mCyan Text\033[0m"

其中:”\033[31m”、”\033[31m”、”\033[0m”等是ANSI转义序列(ANSI escape code/sequence),它控制文本输出的格式、颜色等。

格式 : 
 \033[显示方式;字体颜色;背景颜色m 中间是变颜色的内容 \033[0m

其中各个参数意义如下:

字体色            背景色           颜色
---------------------------------------------
30                40              黑色
31                41              红色
32                42              绿色
33                43              黃色
34                44              蓝色
35                45              紫红色
36                46              青蓝色
37                47              白色

显示方式           意义
-----------------------------------
0                终端默认设置
1                高亮显示
4                使用下划线
5                闪烁
7                反白显示
8                不可见

那么添加颜色就可以如下处理:

#define OUT_RED     "\033[0;31;1m"
#define OUT_GREEN   "\033[0;32;1m"
#define OUT_END     "\033[0m"

#define FILE_INFO       "[" << __FILE__ << '@' << __FUNCTION__ << '#' << __LINE__  << "]"
#define logRed(x)       std::cout << OUT_RED << FILE_INFO << #x":" << x << OUT_END << std::endl
#define logGreen(x)     std::cout << OUT_GREEN << FILE_INFO << #x":" << x << OUT_END << std::endl

...

int delay = 5;
int other = 3;

logRed(delay);
logGreen(other);

多个变量的实现

最后一个问题,在多变量的时候,如何输出呢?
对于多变量的输出,就是要弄清楚究竟有多少个变要输出,这样就可以扩展宏,进行足个输出即可。

最简单的方案

非常傻瓜的方式可以直接如下:

#define logs1(a)       std::cout << OUT_GREEN << FILE_INFO << #a":" << a << OUT_END << std::endl
#define logs2(a,b)     std::cout << OUT_GREEN << FILE_INFO << #a":" << a << ", "#b":" << b << OUT_END << std::endl
#define logs3(a,b,c)   std::cout << OUT_GREEN << FILE_INFO << #a":" << a << ", "#b":" << b << ", "#c":" << c << OUT_END << std::endl
#define logs4...
#define logs5...
...

int one = 5;
int two = 3;
int three = 2;

logs1(one);
logs2(one, two);
logs3(one, two, three);

即多少个变量,对应用哪一个函数输出;由于宏不能够重载,所以不能用相同的名字!
但是这种方式有如下几个问题:

  • 输出函数名称会很多。
  • 还要数参数个数,与函数名对应。
  • 如果想要不同颜色输出,则又要添加新的函数名。

计算参数个数

因为C/C++多参数输入,可以直接用 “…”代替,比如常见的 printf 函数就是如此!原型如下:

int __cdecl printf(const char * _Format,...);

只要我们能够动态计算出参数个数,就可以通过映射的方式,绑定到对应参数数目的输出函数上面。
那么如何计算呢?
经过苦苦查找,让我找到如下的方式:

#define ARG_COUNT_PRIVATE(_0,  _1,  _2,  _3,  _4,  _5,  _6,  _7,  _8, N, ...) N
#define ARG_COUNT(...)      ARG_COUNT_PRIVATE(0, __VA_ARGS__, 8,  7,  6,  5,  4,  3,  2,  1,  0)

#define FILE_INFO   "[" << __FILE__ << '@' << __FUNCTION__ << '#' << __LINE__  << "]"
#define logs(a)       std::cout << OUT_GREEN << FILE_INFO << #a":" << a << OUT_END << std::endl

...

int one = 5;
int two = 3;
int three = 2;

logs(ARG_COUNT(one));
logs(ARG_COUNT(one, two));
logs(ARG_COUNT(one, two, three));

输出结果:

[..\cppdemo\main.cpp@main#30]ARG_COUNT(one):1
[..\cppdemo\main.cpp@main#31]ARG_COUNT(one, two):2
[..\cppdemo\main.cpp@main#32]ARG_COUNT(one, two, three):3

然后只要我们在用C++宏里面的 “##”进行连接,就可以将 logs ## Num 变成对应的函数。

最后的源码

所有的问题都已经解决了,那么最后的代码就如下了。

#ifndef LOGGER_H
#define LOGGER_H

///================= package define =====================
#define ARG_COUNT_PRIVATE(\
     _0,  _1,  _2,  _3,  _4,  _5,  _6,  _7,  _8,  _9, \
    _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \
    _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \
    _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \
    _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, \
    _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \
    _60, _61, _62, _63, _64, N, ...) N

#define ARG_COUNT(...)      ARG_COUNT_PRIVATE(0, __VA_ARGS__,\
    64, 63, 62, 61, 60, \
    59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \
    49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \
    39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \
    29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \
    19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \
     9,  8,  7,  6,  5,  4,  3,  2,  1,  0)

#define FUN_COUNT_GLUE(M,count)     M##count
#define FUN_JOIN_COUNT(M,count)     FUN_COUNT_GLUE(M,count)
#define FUN_JOIN_ARGS(x, y)     x y

#define CallSomeOne(fn, ...)    FUN_JOIN_ARGS(FUN_JOIN_COUNT(fn, ARG_COUNT(__VA_ARGS__)), (__VA_ARGS__))

///================= logger =====================
///  日志输出
///
#if defined QS_LOG
#include "QsLog.h"
#define PR QLOG_INFO() // QsLog 输出(一个用Qt封装的日志类,挺好用的,在此推荐一下)
#define ENDL ""
#elif defined QT_CORE_LIB  // Qt 标准输出
#include <QDebug>
#define PR qDebug()
#define ENDL ""
#elif defined __cplusplus
#include <iostream>
using namespace std;
#define PR std::cout
#define ENDL std::endl
#endif

#define OUT_RED     "\033[0;31;1m"
#define OUT_GREEN   "\033[0;32;1m"
#define OUT_YELLOW  "\033[0;33;1m"
#define OUT_BLUE    "\033[0;34;1m"
#define OUT_END     "\033[0m"

#define FILE_INFO   "[" << __FILE__ << '@' << __FUNCTION__ << '#' << __LINE__  << "]"

#define param1(a)               #a":" << a
#define param2(a,b)             #a":" << a << ", "#b":" << b
#define param3(a,b,c)           #a":" << a << ", "#b":" << b << ", "#c":" << c
#define param4(a,b,c,d)         #a":" << a << ", "#b":" << b << ", "#c":" << c << ", "#d":" << d

#define pr0()           "null param out"
#define pr1(...)        param1(__VA_ARGS__)
#define pr2(...)        param2(__VA_ARGS__)
#define pr3(...)        param3(__VA_ARGS__)
#define pr4(...)        param4(__VA_ARGS__)

#define pr5(a,b,c,d,e)              pr3(a,b,c) << ", " << param2(d,e)
#define pr6(a,b,c,d,e,f)            pr3(a,b,c) << ", " << param3(d,e,f)
#define pr7(a,b,c,d,e,f,g)          pr4(a,b,c,d) << ", " << param3(e,f,g)
#define pr8(a,b,c,d,e,f,g,h)        pr4(a,b,c,d) << ", " << param4(e,f,g,h)
#define pr9(a,b,c,d,e,f,g,h,i)      pr8(a,b,c,d,e,f,g,h) << ", " << param1(i)
#define pr10(a,b,c,d,e,f,g,h,i,j)   pr9(a,b,c,d,e,f,g,h,i) << ", " << param1(j)
//....  有兴趣大家可以继续扩充

#define logStr(x)       PR << FILE_INFO << x << ENDL  // 原样输出,无需格式化

#define logDebug(...)   PR << ""         << "DEBUG " << FILE_INFO << CallSomeOne(pr, __VA_ARGS__) << ENDL
#define logInfo(...)    PR << OUT_GREEN  << "INFO  " << FILE_INFO << CallSomeOne(pr, __VA_ARGS__) << OUT_END << ENDL
#define logWarn(...)    PR << OUT_YELLOW << "WARN  " << FILE_INFO << CallSomeOne(pr, __VA_ARGS__) << OUT_END << ENDL
#define logError(...)   PR << OUT_RED    << "ERROR " << FILE_INFO << CallSomeOne(pr, __VA_ARGS__) << OUT_END << ENDL
#endif // LOGGER_H

总结

由于受到工作方向的影响,有很多用法被局限在我们日常的工作方向中,不能很好的做出符合大家各自场景的东西。 然而我觉得学习,除了学到东西,更应该获取的是思维方式。 我的砖就抛到这里,希望对你们有用。 接下来的路就请各位小伙伴们自己走了!