如何给printf打印增加颜色、时间戳、文件名、行号、函数名
本文介绍了在嵌入式Linux开发中优化日志打印的方法,通过添加颜色、等级标识等额外信息提高调试效率。首先解释了终端颜色控制原理(如"\033[31m"表示红色),并给出了基础颜色打印示例。然后展示了通过宏定义封装不同颜色等级的打印函数(如ERROR用红色,WARNING用黄色)。文中还详细说明了可变参数宏##__VA_ARGS__的作用,以及如何避免空参数导致的语法错误。最后提
在嵌入式Linux开发中,日志的打印用于排查问题,普通的printf打印,仅仅是打印数据本身,为了进一步更方便的排查问题,可以对打印数据的本身,增加一些额外的信息,比如:
- 给打印的数据增加不同的颜色展示,通过颜色可以直观的区分不同的打印
- 给打印的数据增加其被调用的文件名、行号和函数名,在大型的项目中,增加这些信息,方便定位代码的位置
- 给打印的数据增加时间戳,当需要了解程序运行的具体时间时,加上时间戳很有用
1 打印颜色
printf打印可以显示不同的颜色,其实和系统里的终端控制有关,终端能识别一些特殊的字符序列,printf 函数通过添加这些序列,就能告诉终端要显示什么颜色。
比如 “\033 [31m” 这个序列,就表示后面的文字用红色显示,“\033 [0m” 是恢复默认颜色。
最直接的写法如下:
// gcc main.c -o main
#include <stdio.h>
// 主测试函数
int main() {
// 基本打印测试
printf("打印测试\n");
// 颜色打印测试
printf("\033[31m红色/RED - 通常用于错误信息\033[0m\n");
printf("\033[32m绿色/GREEN - 通常用于成功信息\033[0m\n");
printf("\033[33m黄色/YELLOW - 通常用于警告信息\033[0m\n");
printf("\033[34m蓝色/BLUE - 通常用于调试信息\033[0m\n");
printf("\033[35m洋红色/MAGENTA - 用于特殊标记\033[0m\n");
printf("\033[36m青色/CYAN - 用于信息提示\033[0m\n");
printf("\033[37m白色/WHITE - 用于普通输出\033[0m\n");
return 0;
}
运行效果如下:

1.1 打印颜色基础封装
可以给不同颜色的打印增加封装,这样就不用在调用打印的地方每次都增加这些颜色控制符了。
// gcc main.c -o main
#include <stdio.h>
/* 彩色打印宏 */
#define PRINT(fmt, ...) printf("\033[37m" fmt "\033[0m", ##__VA_ARGS__)
#define PRINT_RED(fmt, ...) printf("\033[31m" fmt "\033[0m", ##__VA_ARGS__)
#define PRINT_GREEN(fmt, ...) printf("\033[32m" fmt "\033[0m", ##__VA_ARGS__)
#define PRINT_YELLOW(fmt, ...) printf("\033[33m" fmt "\033[0m", ##__VA_ARGS__)
#define PRINT_BLUE(fmt, ...) printf("\033[34m" fmt "\033[0m", ##__VA_ARGS__)
#define PRINT_MAGENTA(fmt, ...) printf("\033[35m" fmt "\033[0m", ##__VA_ARGS__)
#define PRINT_CYAN(fmt, ...) printf("\033[36m" fmt "\033[0m", ##__VA_ARGS__)
#define PRINT_WHITE(fmt, ...) printf("\033[37m" fmt "\033[0m", ##__VA_ARGS__)
// 主测试函数
int main() {
// 基本打印测试
PRINT("打印测试:%d\n", 1);
// 颜色打印测试
PRINT_RED("红色/RED - 通常用于错误信息\n");
PRINT_GREEN("绿色/GREEN - 通常用于成功信息\n");
PRINT_YELLOW("黄色/YELLOW - 通常用于警告信息\n");
PRINT_BLUE("蓝色/BLUE - 通常用于调试信息\n");
PRINT_MAGENTA("洋红色/MAGENTA - 用于特殊标记\n");
PRINT_CYAN("青色/CYAN - 用于信息提示\n");
PRINT_WHITE("白色/WHITE - 用于普通输出\n");
return 0;
}

更进一步,可以把颜色控制符通过宏定义来先定义出来,方便理解代码含义。
/* 颜色定义 */
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[32m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_BLUE "\033[34m"
#define COLOR_MAGENTA "\033[35m"
#define COLOR_CYAN "\033[36m"
#define COLOR_WHITE "\033[37m"
/* 彩色打印宏 */
#define PRINT(fmt, ...) printf(COLOR_WHITE fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define PRINT_RED(fmt, ...) printf(COLOR_RED fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define PRINT_GREEN(fmt, ...) printf(COLOR_GREEN fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define PRINT_YELLOW(fmt, ...) printf(COLOR_YELLOW fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define PRINT_BLUE(fmt, ...) printf(COLOR_BLUE fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define PRINT_MAGENTA(fmt, ...) printf(COLOR_MAGENTA fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define PRINT_CYAN(fmt, ...) printf(COLOR_CYAN fmt COLOR_RESET "\n", ##__VA_ARGS__)
#define PRINT_WHITE(fmt, ...) printf(COLOR_WHITE fmt COLOR_RESET "\n", ##__VA_ARGS__)
这里需要了解可变参数的使用:
...:表示可变参数部分,可以是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
可以进一步封装:
// gcc main.c -o main
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
/* 颜色定义 */
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[32m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_BLUE "\033[34m"
#define COLOR_MAGENTA "\033[35m"
#define COLOR_CYAN "\033[36m"
#define COLOR_WHITE "\033[37m"
/* 自定义颜色打印宏 - 基础实现 */
#define PRINT_COLOR(color, level, fmt, ...) \
do { \
printf("%s[%s] " fmt COLOR_RESET "\n", \
color, level, ##__VA_ARGS__); \
} while(0)
/* 彩色打印宏 - 复用基础宏 */
#define PRINT(fmt, ...) PRINT_COLOR(COLOR_WHITE, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_RED(fmt, ...) PRINT_COLOR(COLOR_RED, "ERRO", fmt, ##__VA_ARGS__)
#define PRINT_GREEN(fmt, ...) PRINT_COLOR(COLOR_GREEN, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_YELLOW(fmt, ...) PRINT_COLOR(COLOR_YELLOW, "WARN", fmt, ##__VA_ARGS__)
#define PRINT_BLUE(fmt, ...) PRINT_COLOR(COLOR_BLUE, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_MAGENTA(fmt, ...) PRINT_COLOR(COLOR_MAGENTA, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_CYAN(fmt, ...) PRINT_COLOR(COLOR_CYAN, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_WHITE(fmt, ...) PRINT_COLOR(COLOR_WHITE, "INFO", fmt, ##__VA_ARGS__)
// 主测试函数
int main() {
// 基本打印测试
PRINT("打印测试");
// 颜色打印测试
PRINT_RED("红色/RED - 通常用于错误信息");
PRINT_GREEN("绿色/GREEN - 通常用于成功信息");
PRINT_YELLOW("黄色/YELLOW - 通常用于警告信息");
PRINT_BLUE("蓝色/BLUE - 通常用于调试信息");
PRINT_MAGENTA("洋红色/MAGENTA - 用于特殊标记");
PRINT_CYAN("青色/CYAN - 用于信息提示");
PRINT_WHITE("白色/WHITE - 用于普通输出");
return 0;
}
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如下:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (main)
# 指定生成目标
add_executable(main main.c)
测试代码:
// gcc main.c -o main
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
/* 颜色定义 */
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[32m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_BLUE "\033[34m"
#define COLOR_MAGENTA "\033[35m"
#define COLOR_CYAN "\033[36m"
#define COLOR_WHITE "\033[37m"
/* 自定义颜色打印宏 - 基础实现 */
#define PRINT_COLOR(color, level, fmt, ...) \
do { \
printf("%s[%s] %s:%d [%s] " fmt COLOR_RESET "\n", \
color, level, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
} while(0)
/* 彩色打印宏 - 复用基础宏 */
#define PRINT(fmt, ...) PRINT_COLOR(COLOR_WHITE, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_RED(fmt, ...) PRINT_COLOR(COLOR_RED, "ERRO", fmt, ##__VA_ARGS__)
#define PRINT_GREEN(fmt, ...) PRINT_COLOR(COLOR_GREEN, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_YELLOW(fmt, ...) PRINT_COLOR(COLOR_YELLOW, "WARN", fmt, ##__VA_ARGS__)
#define PRINT_BLUE(fmt, ...) PRINT_COLOR(COLOR_BLUE, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_MAGENTA(fmt, ...) PRINT_COLOR(COLOR_MAGENTA, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_CYAN(fmt, ...) PRINT_COLOR(COLOR_CYAN, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_WHITE(fmt, ...) PRINT_COLOR(COLOR_WHITE, "INFO", fmt, ##__VA_ARGS__)
// 主测试函数
int main() {
// 基本打印测试
PRINT("打印测试");
// 颜色打印测试
PRINT_RED("红色/RED - 通常用于错误信息");
PRINT_GREEN("绿色/GREEN - 通常用于成功信息");
PRINT_YELLOW("黄色/YELLOW - 通常用于警告信息");
PRINT_BLUE("蓝色/BLUE - 通常用于调试信息");
PRINT_MAGENTA("洋红色/MAGENTA - 用于特殊标记");
PRINT_CYAN("青色/CYAN - 用于信息提示");
PRINT_WHITE("白色/WHITE - 用于普通输出");
return 0;
}

2.2 打印文件名(不带路径)
如果想要仅打印文件名,不带路径,可以对文件名进行提取,原理就是查找路径中最后一个 / 的位置:
- 若存在:返回该位置的指针,
strrchr(__FILE__, '/') + 1即为文件名的起始地址 - 若不存在(如直接使用文件名):返回
__FILE__本身
/* 获取文件名(不含路径) */
#define __SHORT_FILE__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
/* 自定义颜色打印宏 - 基础实现 */
#define PRINT_COLOR(color, level, fmt, ...) \
do { \
printf("%s[%s] %s:%d [%s] " fmt COLOR_RESET "\n", \
color, level, __SHORT_FILE__, __LINE__, __func__, ##__VA_ARGS__); \
} while(0)

3 打印时间
3.1 打印时间(年月日时分秒)
这里使用一个自定义的__CURRENT_TIME__的宏定义来获取时间:
/* 获取当前时间字符串 */
#define __CURRENT_TIME__ \
({ \
time_t __t = time(NULL); \
struct tm *__tm = localtime(&__t); \
static char __buf[26]; \
strftime(__buf, sizeof(__buf), "%Y-%m-%d %H:%M:%S", __tm); \
__buf; \
})
时间处理流程为:
- 获取时间戳:
time(NULL)返回当前时间的秒数(自 1970 年 1 月 1 日以来)。 - 转换为本地时间:
localtime(&__t)将时间戳转换为本地时间(考虑时区)。 - 格式化字符串:
strftime按照%Y-%m-%d %H:%M:%S格式生成可读时间字符串。
// gcc main.c -o main
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
/* 颜色定义 */
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[32m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_BLUE "\033[34m"
#define COLOR_MAGENTA "\033[35m"
#define COLOR_CYAN "\033[36m"
#define COLOR_WHITE "\033[37m"
/* 获取当前时间字符串 */
#define __CURRENT_TIME__ \
({ \
time_t __t = time(NULL); \
struct tm *__tm = localtime(&__t); \
static char __buf[26]; \
strftime(__buf, sizeof(__buf), "%Y-%m-%d %H:%M:%S", __tm); \
__buf; \
})
/* 获取文件名(不含路径) */
#define __SHORT_FILE__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
/* 自定义颜色打印宏 - 基础实现 */
#define PRINT_COLOR(color, level, fmt, ...) \
do { \
printf("%s%s[%s] %s:%d [%s] " fmt COLOR_RESET "\n", \
color, __CURRENT_TIME__, level, __SHORT_FILE__, __LINE__, __func__, ##__VA_ARGS__); \
} while(0)
/* 彩色打印宏 - 复用基础宏 */
#define PRINT(fmt, ...) PRINT_COLOR(COLOR_WHITE, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_RED(fmt, ...) PRINT_COLOR(COLOR_RED, "ERRO", fmt, ##__VA_ARGS__)
#define PRINT_GREEN(fmt, ...) PRINT_COLOR(COLOR_GREEN, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_YELLOW(fmt, ...) PRINT_COLOR(COLOR_YELLOW, "WARN", fmt, ##__VA_ARGS__)
#define PRINT_BLUE(fmt, ...) PRINT_COLOR(COLOR_BLUE, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_MAGENTA(fmt, ...) PRINT_COLOR(COLOR_MAGENTA, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_CYAN(fmt, ...) PRINT_COLOR(COLOR_CYAN, "INFO", fmt, ##__VA_ARGS__)
#define PRINT_WHITE(fmt, ...) PRINT_COLOR(COLOR_WHITE, "INFO", fmt, ##__VA_ARGS__)
// 主测试函数
int main() {
// 基本打印测试
PRINT("打印测试");
// 颜色打印测试
PRINT_RED("红色/RED - 通常用于错误信息");
PRINT_GREEN("绿色/GREEN - 通常用于成功信息");
PRINT_YELLOW("黄色/YELLOW - 通常用于警告信息");
PRINT_BLUE("蓝色/BLUE - 通常用于调试信息");
PRINT_MAGENTA("洋红色/MAGENTA - 用于特殊标记");
PRINT_CYAN("青色/CYAN - 用于信息提示");
PRINT_WHITE("白色/WHITE - 用于普通输出");
return 0;
}

3.2 打印时间(带毫秒)
如果需要更精确的时间,比如精确到毫秒,可以使用使用 gettimeofday 获取微秒级时间(tv_usec 字段),并通过 /1000 转换为毫秒
#include <sys/time.h>
/* 获取当前时间字符串(带毫秒) */
#define __CURRENT_TIME__ \
({ \
struct timeval __tv; \
gettimeofday(&__tv, NULL); \
struct tm *__tm = localtime(&__tv.tv_sec); \
static char __buf[32]; \
snprintf(__buf, sizeof(__buf), "%02d-%02d-%02d %02d:%02d:%02d.%03d", \
__tm->tm_year % 100, __tm->tm_mon + 1, __tm->tm_mday, \
__tm->tm_hour, __tm->tm_min, __tm->tm_sec, \
(int)(__tv.tv_usec / 1000)); \
__buf; \
})

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



所有评论(0)