开篇声明:这篇随笔只是谈谈做运行日志服务积累十多年的心得、经验,无意于说教。所以谁若是有想法,或是不同意,请保留或是说出来,拒绝动粗。此外,凡事没有最好,愿意借用或是借鉴源码的,可以尽情按需修改,如有需要可以联系,哥不确定能帮忙。
什么叫运行日志服务?这里说的,其实就是怎么用printf。十多年前刚开始做项目的时候,哥还是沿用学生时候的做法,在程序需要的地方用printf进行日志打印,有两种做法。
方法 A.
if(!ptr) { #ifdef _DEBUG printf(, __FUNCTION__, __LINE__); #endif return -1; }
方法 B.
if(!ptr) { if(g_debug) { printf(, __FUNCTION__, __LINE__); } return -1; }
我相信,很多人看到这些就觉得眼熟。比如尚在学习编程的,会说“啊,我咋没想到可以用编译宏或是变量控制呢?”,经验丰富的会说“嗯,哥当年就是这么干的!”,当然,也肯定有人说“这样不是挺好么!”。确实,这样也没什么特比不好的,反正最后发布的版本都能屏蔽那些不需要的打印。
哥想说,你这么写程序,不觉得累么?再者,重新编译哦,查个错还要重新编译啊,那能保证客户那边跑的和你查问题的这个版本是一回事么?哥真见过哥超牛气的嵌入式团队,软件就是这么做的,每天大量的时间就这么花在编译上了!方法B好似进步了点,但系统会不会被太多的打印类似?再进一步,运行日志是不是要能够搞进文件呢?
其实,嵌入式系统开发,运行日志远远比一般人能想到的要复杂,当然做linux等程序也不简单。嵌入式系统里,日志输出要关心调用发生时所处的运行栈:
这几种运行栈下日志输出行为是不同的。应用栈上的日志输出,大多是用来定位程序运行错误,因此提供调用函数名和源文件行号很重要;中断栈是不能进行任何直接输出的,日志输出需要发送给一个日志队列,由日志服务程序打印出来;SHELL栈则主要是用于人机交互,因此一般要屏蔽函数名这些信息,以便输出排版更好看。
既然有这么多行栈,那各搞一套日志打印不就可以了?可以这么做,但不是很合适。比如说,有些函数,可以被同时用于应用程序和SHELL调用,甚至被中断程序调用。对于这些两栖甚至不确定的函数,按这样一个思路,我们唯有删除任何打印。
我们需要一个统一的日志打印接口。这个打印函数,可以让程序员们在需要的时候直接调用,至于程序运行状态这些高深的东西都留给这个打印函数自己来做吧!
此外,我们希望这个日志打印接口,能允许定制输出级别,别只是一股脑儿的打印或是全部不打印。
再者,我们还希望日志打印能针对各个模块,或是运行任务进行输出控制。
最后,如果日志打印能让我们随心所欲的调整输出到哪里去,就太好了!
最后的最后,这玩意得用起来简单,快捷,最好别有什么使用限制!
如果你有这样的需求,且正在寻找解决办法,那不妨下载下面的C源文件,直接移植或是借鉴。
zTrace.c,zTrace.h,zTraceApi.h,以及zLog.c,salLog.c就是zTrace的全部实现代码了。
zTraceApi.h是唯一的应用程序必须包含的头问题,一起来看看zTraceApi.h里有哪些东西。
/*---------------------------------------------------------- File Name : xxx.h Description: This file should be included by any products, no matter on vxworks or windows. Author : hhao020@gmail.com (bug fixing and consulting) Date : 2007-05-15 ------------------------------------------------------------*/ #ifndef __TRACE_API_H__ #define __TRACE_API_H__ #include #ifdef __cplusplus { #endif int z_IamShell(); int z_TaskSelf(); int z_InShell(); int z_InKernal(); int z_InInterrupt(); *, int)); *, int)); *, int)); *, int)); *fmt, ...); *fmt, ...); *fmt, ...); extern int z_ShellLogHex(const byte_t *pData, int nLen); extern int z_IntLogHex (const byte_t *pData, int nLen); extern int z_TaskLogHex (const byte_t *pData, int nLen); *fmt, ...); extern int z_ShellPrintHex(const byte_t *pData, int nLen); int zTraceTaskLevel(); int zTraceBlockOn(); int zTraceBlockOff(); int zTraceServiceInit(); int zTraceLevelReset(); int zTraceLevelSet(int tid, byte_t ucLevel); int zTraceFlagSet(int tid, int bLogFlag); int zTraceLevelSetAll(byte_t ucLevel); int zTraceMemoryReset(); int zTraceMemoryInit(int size); int zTraceMemoryShow(int detail); TraceFatal 0x01 /*fatal errors, not recoverable, and is starting reboot soon*/ #define TraceAlarm 0x02 /*emergent errors, not recoverable, need users' maintence check.*/ #define TraceError 0x04 /*software errors*/ #define TraceWarn 0x08 /*configure errors*/ #define TraceInfo 0x10 /*key processes like message traces, for designer, tester's purpose*/ #define TraceDebug 0x20 /*any other verbose information for designing, debuging intents*/ #define VERBOSE_LINE(fnPrint, level) fnPrint("[%s]:%s %s %d::", _STR(level), __FILE__, __FUNCTION__, __LINE__) #define zTraceV(level, fmt, arg...) do{ \ if(zTraceTaskLevel() & _CONS(Trace,level)) {\ zTraceBlockOn(); \ VERBOSE_LINE(z_TaskLog, level); \ z_TaskLog(fmt, ##arg); \ zTraceBlockOff(); \ } \ }while(0) #define zTraceQ(level, fmt, arg...) do{ \ if(zTraceTaskLevel() & _CONS(Trace,level)) {\ zTraceBlockOn(); \ z_TaskLog(fmt, ##arg); \ zTraceBlockOff(); \ } \ }while(0) #define zTraceP(fmt, arg...) do{ \ zTraceBlockOn(); \ z_ShellPrint(fmt, ##arg); \ zTraceBlockOff(); \ }while(0) #define zTracePV(fmt, arg...) do{ \ zTraceBlockOn(); \ VERBOSE_LINE(z_TaskLog, Console); \ z_ShellPrint(fmt, ##arg); \ zTraceBlockOff(); \ }while(0) #define zTraceHexV(level, pData, nLen) do{ \ if(zTraceTaskLevel() & _CONS(Trace,level)) {\ zTraceBlockOn(); \ VERBOSE_LINE(z_TaskLog, level); \ z_TaskLogHex((pData), nLen); \ zTraceBlockOff(); \ } \ }while(0) #define zTraceHexQ(level, pData, nLen) do{ \ if(zTraceTaskLevel() & _CONS(Trace,level)) {\ zTraceBlockOn(); \ z_TaskLogHex((pData), nLen); \ zTraceBlockOff(); \ } \ }while(0) #define zTraceHexP(pData, nLen) do{ \ zTraceBlockOn(); \ z_ShellPrintHex((byte_t*)(pData), nLen); \ zTraceBlockOff(); \ }while(0) #define zTraceFatal(fmt, arg...) zTraceV(Fatal, fmt, ##arg) #define zTraceAlarm(fmt, arg...) zTraceV(Alarm, fmt, ##arg) #define zTraceError(fmt, arg...) zTraceV(Error, fmt, ##arg) #define zTraceWarn( fmt, arg...) zTraceV(Warn, fmt, ##arg) #define zTraceInfo( fmt, arg...) zTraceV(Info, fmt, ##arg) #define zTraceDebug(fmt, arg...) zTraceV(Debug, fmt, ##arg) #define zTraceFatalQ(fmt, arg...) zTraceQ(Fatal , fmt, ##arg) #define zTraceAlarmQ(fmt, arg...) zTraceQ(Alarm , fmt, ##arg) #define zTraceErrorQ(fmt, arg...) zTraceQ(Error , fmt, ##arg) #define zTraceWarnQ( fmt, arg...) zTraceQ(Warn , fmt, ##arg) #define zTraceInfoQ( fmt, arg...) zTraceQ(Info, fmt, ##arg) #define zTraceDebugQ(fmt, arg...) zTraceQ(Debug , fmt, ##arg) #define zTraceHexFatal(pData, nLen) zTraceHexV(Fatal, (byte_t*)(pData), nLen) #define zTraceHexAlarm(pData, nLen) zTraceHexV(Alarm, (byte_t*)(pData), nLen) #define zTraceHexError(pData, nLen) zTraceHexV(Error, (byte_t*)(pData), nLen) #define zTraceHexWarn( pData, nLen) zTraceHexV(Warn, (byte_t*)(pData), nLen) #define zTraceHexInfo( pData, nLen) zTraceHexV(Info, (byte_t*)(pData), nLen) #define zTraceHexDebug(pData, nLen) zTraceHexV(Debug, (byte_t*)(pData), nLen) #define zTraceHexFatalQ(pData, nLen) zTraceHexQ(Fatal , (byte_t*)(pData), nLen) #define zTraceHexAlarmQ(pData, nLen) zTraceHexQ(Alarm , (byte_t*)(pData), nLen) #define zTraceHexErrorQ(pData, nLen) zTraceHexQ(Error , (byte_t*)(pData), nLen) #define zTraceHexWarnQ( pData, nLen) zTraceHexQ(Warn , (byte_t*)(pData), nLen) #define zTraceHexInfoQ( pData, nLen) zTraceHexQ(Info, (byte_t*)(pData), nLen) #define zTraceHexDebugQ(pData, nLen) zTraceHexQ(Debug , (byte_t*)(pData), nLen) #define PRINT_THREAD_INFO(tid) do{ printf("%s: %d\n", _STR(tid), (int)tid); }while(0) #define PRINT_THREAD_LIST(tlist, n) do{ \ int i; \ , _STR(tlist), i, (int)tlist[i]); \ }while(0) #ifdef __cplusplus } #endif #endif /*__TRACE_API_H__*/
View Code