在嵌入式Linux开发中,日志的打印用于排查问题,普通的printf打印,仅仅是打印数据本身,为了进一步更方便的排查问题,可以对打印数据的本身,增加一些额外的信息,比如:
- 给打印的数据增加不同的颜色展示,通过颜色可以直观的区分不同的打印
- 给打印的数据增加其被调用的文件名、行号和函数名,在大型的项目中,增加这些信息,方便定位代码的位置
- 给打印的数据增加时间戳,当需要了解程序运行的具体时间时,加上时间戳很有用
1 打印颜色
printf打印可以显示不同的颜色,其实和系统里的终端控制有关,终端能识别一些特殊的字符序列,printf 函数通过添加这些序列,就能告诉终端要显示什么颜色。
比如 “\033 [31m” 这个序列,就表示后面的文字用红色显示,“\033 [0m” 是恢复默认颜色。
最直接的写法如下:
运行效果如下:

1.1 打印颜色基础封装
可以给不同颜色的打印增加封装,这样就不用在调用打印的地方每次都增加这些颜色控制符了。

更进一步,可以把颜色控制符通过宏定义来先定义出来,方便理解代码含义。
这里需要了解可变参数的使用:
...:表示可变参数部分,可以是0个至多个##__VA_ARGS__:C 预处理器定义的特殊标识符,代表宏调用时传递的可变参数列表(即省略号...对应的参数)
例如,宏定义 #define PRINT(fmt, ...) printf(fmt, __VA_ARGS__),当调用 PRINT("%d", 10) 时,__VA_ARGS__ 会被替换为 10。
当可变参数列表为空时,直接使用
__VA_ARGS__会导致多余的逗号(如fprintf(stderr, fmt, __VA_ARGS__)变为fprintf(stderr, fmt, ),编译错误)。因此,
##的作用是用来消除多余逗号,作用是:
- 当
__VA_ARGS__非空时,##无实际效果(仅作为分隔符)- 当
__VA_ARGS__为空时,##会删除其前面的逗号,避免语法错误
1.2 宏定义封装
如果需要再增加一些额外信息,比如增加打印等级level,红色对应ERROR,黄色对应WARNING,其它是INFO
可以进一步封装:
2 打印文件名、行号和函数名
打印文件名、行号和函数名,需要用到__FILE__、__LINE__ 和 __func__ 这些特殊标识符。
__FILE__:当前正在编译的源文件的路径名(由编译器决定,可能是绝对路径或相对路径),例如:若源文件为 /home/user/project/main.c,则__FILE__的值为 “main.c” 或 “/home/user/project/main.c”(取决于编译器)。__LINE__:当前代码行在源文件中的行号__func__:当前所在函数的名称
2.1 打印文件名(带路径)
先来测试打印带路径的文件名,可以通过cmake的方式来编译程序,这样__FILE__对应的完整的路径和文件名。
测试代码对应的CMakeLists.txt如下:
测试代码:

2.2 打印文件名(不带路径)
如果想要仅打印文件名,不带路径,可以对文件名进行提取,原理就是查找路径中最后一个 / 的位置:
- 若存在:返回该位置的指针,
strrchr(__FILE__, '/') + 1即为文件名的起始地址 - 若不存在(如直接使用文件名):返回
__FILE__本身

3 打印时间
3.1 打印时间(年月日时分秒)
这里使用一个自定义的__CURRENT_TIME__的宏定义来获取时间:
时间处理流程为:
- 获取时间戳:
time(NULL)返回当前时间的秒数(自 1970 年 1 月 1 日以来)。 - 转换为本地时间:
localtime(&__t)将时间戳转换为本地时间(考虑时区)。 - 格式化字符串:
strftime按照%Y-%m-%d %H:%M:%S格式生成可读时间字符串。

3.2 打印时间(带毫秒)
如果需要更精确的时间,比如精确到毫秒,可以使用使用 gettimeofday 获取微秒级时间(tv_usec 字段),并通过 /1000 转换为毫秒

4 总结
本篇介绍了如何给printf增加额外的打印信息,包括颜色、时间戳、调用的文件名、行号、函数名,并通过实际的例子进行验证。

所有评论(0)