C语言实现一个简单便捷的日志打印宏

#C语言实现一个简单便捷的日志打印宏

[调试手段] [日志系统]

在写应用的时候难免会遇到一些要调试的代码段,习惯了Windows下IDE的调试手段的人,刚开始面对嵌入式的交叉编译环境的时候一般会想有什么好的调试手段吧,这里总结一下最传统的调试方法:打印日志。并通过C语言实现一个简单便捷的日志打印宏。


文章目录


##Sample

在程序运行过程中,如果我们想调试某段代码,那么一般在那段代码中加printf(); 打印出想看的信息即可.

printf("Sample print\n");

但如果我们遇到如下类型,存在多数相同日志信息的时候该如何具体定位那一段代码出现了问题呢?

		//sample
        s32Ret = SAMPLE_COMM_VENC_Start(VencChn, enPayLoad[2], \
                                        gs_enNorm, enSize[2], enRcMode, u32Profile);
        if (HI_SUCCESS != s32Ret)
        {
            SAMPLE_PRT("Start Venc failed!\n");
            goto END_VENC_1080P_CLASSIC_5;
        }

        s32Ret = SAMPLE_COMM_VENC_BindVpss(VencChn, VpssGrp, VpssChn);
        if (HI_SUCCESS != s32Ret)
        {
            SAMPLE_PRT("Start Venc failed!\n");
            goto END_VENC_1080P_CLASSIC_5;
        }

可以使用编译器预置宏 __TIME__、__LINE__、__FILE__、__FUNCTION__等,来定位打印出现的文件,函数,及行数等.

__TIME__为编译时间,__LINE__当前行数,__FILE__所在文件,__FUNCTION__所在函数.

printf("something debug at time [%s] in function [%s] on line [%d] of file [%s],\n", __TIME__, __FUNCTION__, __LINE__, __FILE__);

Alt text

##自定义宏

可是每个打印都这么写启不是很麻烦,我们可以定义如下的宏来简化.

#define TAG "TEST"

#define LOG_D(...) \
		do{\
			printf("D/%s: ",TAG);\
			printf(__VA_ARGS__);\
			printf(" (%s, %d)\n", __FILE__,__LINE__);\
		}while(0)

#define LOG_W(...) \
		do{\
			printf("W/%s: ",TAG);\
			printf(__VA_ARGS__);\
			printf(" (%s, %d)\n", __FILE__,__LINE__);\
		}while(0)

#define LOG_E(...) \
		do{\
			printf("E/%s: ",TAG);\
			printf(__VA_ARGS__);\
			printf(" (%s, %d)\n", __FILE__,__LINE__);\
		}while(0)
	//测试代码
	LOG_D("something debug");
	LOG_W("something warning");
	LOG_E("something error");

Alt text

##关于注释

如果我们只是需要在调试阶段加这些打印,正常情况下不需要这些打印,当然也可以通过宏实现.

#define TRUE 1
#define FALSE 0

#define TAG "TEST"
#define DEBUG FALSE

#if DEBUG
#define LOG_D(...) \
		do{\
			printf("D/%s: ",TAG);\
			printf(__VA_ARGS__);\
			printf(" (%s, %d)\n", __FILE__,__LINE__);\
		}while(0)
#else
#define LOG_D(...)
#endif

##log应该包含的信息

当然有些情况这类日志还是不能充分的满足我们的要求的例如:
如果我们想知道一个服务拉起一个进程所产生的延迟是多少,这个时候我们的log还需要有执行时间的记录。
总的来说一条log最好能包含以下信息:级别,时间,tickcount,进程id,线程id,filter,Log正文内容,

级别说明
级别这个可以参照大多数的三方库里面的做法,分为INFO,WARNING, ERROR,FATAL等等级别。不过我们目前从方便使用的角度考虑只分了DEBUG和RELEASE两个级别,省的开发人员去写个LOG还要考虑这个LOG到底属于什么类型。
时间这个就很简单了,一般精确到MS就可以了,主要是用于查看程序运行的大致时间信息。
进程ID关于进程ID这个信息需不需要,取决于你的LOG系统存储实际方式了,如果你的LOG数据是每个进程又自己独立的存储文件,那就不需要。如果是多个进程共享同一个存储,那就需要加上这一项。另一点即使你的LOG存储系统是进程独立的,为了支持动态查看LOG信息,最好还是加上这一项。
线程ID在解决多线程问题的时候,这一项信息是必不可少的。
Filter主要用于查找某一类LOG时候方便,这个Filter是开发人员在开发过程中自己定义的,比如开发者xiaowang写了个模块,他为了能在LOG中很容的找到这一类LOG信息,于是就给这些LOG设置了filter为“xiaowang”
LOG正文内容这个就没太多说的,主要就是LOG信息了

对于一个完备的日志系统这些是必要的。便于我们快速的跟踪流程、监控运行状态、定位问题等。
最后再添加一条时间的记录的例子,结束本文。

#define TRUE 1
#define FALSE 0

#define TAG "TEST"
#define DEBUG TRUE

#define TIMEPRINT \
	do{\
	struct timeval now;\
	struct tm *ptime = NULL;\
	suseconds_t mstime;\
	\
	gettimeofday(&now, NULL);\
	ptime = gmtime(&now.tv_sec);\
	mstime = now.tv_usec/10000;\
	\
	printf("%d-%d-%d-%d-%d-%d:%02d  ", 1900 + ptime->tm_year, 1 + ptime->tm_mon, ptime->tm_mday, ptime->tm_hour, ptime->tm_min, ptime->tm_sec, mstime);\
	}while(0);

#if DEBUG
#define LOG_D(...) \
		do{\
			TIMEPRINT\
			printf("D/%s: ",TAG);\
			printf(__VA_ARGS__);\
			printf(" (%s, %d)\n", __FILE__,__LINE__);\
		}while(0);
#else
#define LOG_D(...)
#endif

#define LOG_W(...) \
		do{\
			TIMEPRINT\
			printf("W/%s: ",TAG);\
			printf(__VA_ARGS__);\
			printf(" (%s, %d)\n", __FILE__,__LINE__);\
		}while(0);

#define LOG_E(...) \
		do{\
			TIMEPRINT\
			printf("E/%s: ",TAG);\
			printf(__VA_ARGS__);\
			printf(" (%s, %d)\n", __FILE__,__LINE__);\
		}while(0);

测试代码:

	LOG_D("something debug");
	int i = 999999999;
	while(i--);
	LOG_W("something warning");
	sleep(1);
	LOG_E("something error");

Alt text

总结

通过自行定义的日志打印宏,这样就能便捷的在自己的程序中调试代码了,可以根据自己程序的需要自行定义宏.
当然在大型的项目工程中,例如安卓系统,它们都有一套自己的日志系统,用于监控及排查软件故障。一般会在运行过程中产生日志文件,包括时间、TAG、进程名、消息结构、函数返回值、执行情况等信息。在程序发生崩溃的时候甚至有单独的ANR或core dump等文件生成。通过阅读这些日志和文件,我们能快速跟踪程序流程,排查程序问题,因此熟练的掌握日志系统,并通过日志快速的定位问题,是作为一个软件工程师的基本要求。

本文就写到这了,有机会的话,会再写一个完整日志系统,敬请关注。

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我会尽力使用我所知道的知识来帮助你。 写一个 "Hello World" 驱动的步骤如下: 1. 创建一个新的目录,用来存放驱动代码。 2. 在该目录中创建一个文件,命名为 "hello_world.c"。 3. 在 "hello_world.c" 文件中添加以下代码: ``` #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple Hello World driver"); MODULE_VERSION("0.1"); static int __init hello_world_init(void) { printk(KERN_INFO "Hello, World!\n"); return 0; } static void __exit hello_world_exit(void) { printk(KERN_INFO "Goodbye, World!\n"); } module_init(hello_world_init); module_exit(hello_world_exit); ``` 4. 在该目录中创建一个文件,命名为 "Makefile"。 5. 在 "Makefile" 文件中添加以下代码: ``` obj-m += hello_world.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean ``` 6. 使用以下命令编译驱动: ``` make ``` 7. 使用以下命令加载驱动: ``` insmod hello_world.ko ``` 8. 使用以下命令查看内核日志以确认驱动已加载并正常工作: ``` dmesg ``` 9. 使用以下命令卸载驱动: ``` rmmod hello_world ``` 希望这些步骤能帮助你。如果你在实现过程中遇到 ### 回答2: 编写一个"Hello World"驱动程序需要以下几个步骤: 1. 创建一个新的驱动程序文件:首先,我们需要创建一个新的驱动程序文件,通常以.c或.cpp为扩展名。在这个文件中,我们将定义驱动程序的功能。 2. 导入所需的头文件:在驱动程序文件的开头,我们需要导入一些必要的头文件,如kernel.h、module.h和types.h。这些头文件将提供我们在驱动程序中使用的函数和数据类型的定义。 3. 定义驱动程序函数:主要的驱动程序函数是init_module()和cleanup_module()。init_module()函数将在驱动程序加载时调用,而cleanup_module()函数将在驱动程序卸载时调用。 4. 在init_module()函数中,我们将定义“Hello World”字符串,并使用 printk() 函数将其打印到内核日志中。printk()函数类似于printf()函数,但它将输出发送到内核日志而不是终端。 5. 在cleanup_module()函数中,我们可以在驱动程序卸载时执行一些清理操作,如释放占用的资源。 6. 编译和加载驱动程序:编译驱动程序源代码,通常使用make命令。然后使用insmod命令加载驱动程序。 7. 验证驱动程序:使用 dmesg 命令查看内核日志,应该能够看到“Hello World”字符串打印出来。 8. 卸载驱动程序:使用rmmod命令卸载驱动程序。 总结:编写一个“Hello World”驱动程序需要定义和实现init_module()和cleanup_module()函数,并使用printk()函数将字符串打印到内核日志中。然后编译和加载驱动程序,并使用dmesg命令验证它是否正常工作。 ### 回答3: 编写一个Hello World的驱动程序需要按照以下步骤进行: 1. 首先,需要确定使用的操作系统和开发环境。例如,假设我们选择Linux操作系统C语言作为开发环境。 2. 接下来,创建一个新的C源文件hello_world_driver.c,并在文件中引入必要的头文件。例如,可以包含linux/module.h、linux/init.h和linux/kernel.h。 3. 在源文件中定义模块的初始化和清理函数。模块的初始化函数在驱动加载时被调用,而清理函数在驱动卸载时被调用。例如,可以定义一个名为hello_init的初始化函数和一个名为hello_exit的清理函数。 4. 在初始化函数中,使用printk函数输出Hello World的消息到内核日志中。例如,可以使用printk(KERN_INFO "Hello World\n")。 5. 在清理函数中,可以不做任何操作,因为这是一个简单的Hello World驱动,没有资源需要清理。 6. 在源文件的末尾,使用module_init指定初始化函数和module_exit指定清理函数。例如,可以使用module_init(hello_init)和module_exit(hello_exit)。 7. 编译驱动程序。在Linux终端中,使用make命令进行编译。如果一切正常,将生成一个名为hello_world_driver.ko的内核模块文件。 8. 加载驱动程序。在Linux终端中,使用insmod命令加载驱动程序。例如,可以使用insmod hello_world_driver.ko。 9. 检查内核日志。在Linux终端中,使用dmesg命令查看内核日志。应该能够看到Hello World的消息。 10. 卸载驱动程序。在Linux终端中,使用rmmod命令卸载驱动程序。例如,可以使用rmmod hello_world_driver。再次查看内核日志,应该不再有Hello World的消息。 这样,一个简单的Hello World驱动程序就完成了。当加载驱动程序时,它会输出Hello World的消息到内核日志中。当卸载驱动程序时,它不会有任何副作用。这是一个非常简单的示例,用于演示驱动程序的基本结构和加载过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值