怎樣設計一個運行日誌服務

開篇聲明:這篇隨筆只是談談作運行日誌服務積累十多年的心得、經驗,無心於說教。因此誰如果有想法,或是不一樣意,請保留或是說出來,拒絕動粗。此外,凡事沒有最好,願意借用或是借鑑源碼的,能夠盡情按需修改,若有須要能夠聯繫,哥不肯定能幫忙。linux

什麼叫運行日誌服務?這裏說的,其實就是怎麼用printf。十多年前剛開始作項目的時候,哥仍是沿用學生時候的作法,在程序須要的地方用printf進行日誌打印,有兩種作法。程序員

方法 A.shell

if(!ptr)
{
#ifdef _DEBUG
  printf("%s:%d invalid null ptr\n", __FUNCTION__, __LINE__);
#endif
  return -1;
}

方法 B.編程

if(!ptr)
{
  if(g_debug)
  {
    printf("%s:%d invalid null ptr\n", __FUNCTION__, __LINE__);
  }
  return -1;
}

我相信,不少人看到這些就以爲眼熟。好比尚在學習編程的,會說「啊,我咋沒想到能夠用編譯宏或是變量控制呢?」,經驗豐富的會說「嗯,哥當年就是這麼幹的!」,固然,也確定有人說「這樣不是挺好麼!」。確實,這樣也沒什麼特比很差的,反正最後發佈的版本都能屏蔽那些不須要的打印。windows

哥想說,你這麼寫程序,不以爲累麼?再者,從新編譯哦,查個錯還要從新編譯啊,那能保證客戶那邊跑的和你查問題的這個版本是一回事麼?哥真見過哥超牛氣的嵌入式團隊,軟件就是這麼作的,天天大量的時間就這麼花在編譯上了!方法B好似進步了點,但系統會不會被太多的打印相似?再進一步,運行日誌是否是要可以搞進文件呢?ide

其實,嵌入式系統開發,運行日誌遠遠比通常人能想到的要複雜,固然作linux等程序也不簡單。嵌入式系統裏,日誌輸出要關心調用發生時所處的運行棧:函數

  • 內核;
  • 應用棧;
  • 中斷棧;
  • SHELL棧。

這幾種運行棧下日誌輸出行爲是不一樣的。應用棧上的日誌輸出,大可能是用來定位程序運行錯誤,所以提供調用函數名和源文件行號很重要;中斷棧是不能進行任何直接輸出的,日誌輸出須要發送給一個日誌隊列,由日誌服務程序打印出來;SHELL棧則主要是用於人機交互,所以通常要屏蔽函數名這些信息,以便輸出排版更好看。性能

既然有這麼多行棧,那各搞一套日誌打印不就能夠了?能夠這麼作,但不是很合適。好比說,有些函數,能夠被同時用於應用程序和SHELL調用,甚至被中斷程序調用。對於這些兩棲甚至不肯定的函數,按這樣一個思路,咱們惟有刪除任何打印。學習

咱們須要一個統一的日誌打印接口。這個打印函數,可讓程序員們在須要的時候直接調用,至於程序運行狀態這些高深的東西都留給這個打印函數本身來作吧!編碼

此外,咱們但願這個日誌打印接口,能容許定製輸出級別,別隻是一古腦兒的打印或是所有不打印。

再者,咱們還但願日誌打印能針對各個模塊,或是運行任務進行輸出控制。

最後,若是日誌打印能讓咱們爲所欲爲的調整輸出到哪裏去,就太好了!

最後的最後,這玩意得用起來簡單,快捷,最好別有什麼使用限制!

若是你有這樣的需求,且正在尋找解決辦法,那不妨下載下面的C源文件,直接移植或是借鑑。
http://files.cnblogs.com/files/hhao020/cshell_prj_re0.003.rar
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 "zType_Def.h"

#ifdef __cplusplus
extern "C" {
#endif


int z_IamShell();
int z_TaskSelf();
int z_InShell();
int z_InKernal();
int z_InInterrupt();


extern int z_Log2ConsoleCbkSet(int (*fnPrint)(const char *, int));
extern int z_Log2ZcnCbkSet(int (*fnPrint)(const char *, int));
extern int z_Log2FileCbkSet(int (*fnPrint)(const char *, int));
extern int z_Log2MemoryCbkSet(int (*fnPrint)(const char *, int));

extern int z_ShellLog(const char *fmt, ...);
extern int z_IntLog  (const char *fmt, ...);
extern int z_TaskLog (const char *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);

extern int z_ShellPrint(const char *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);



/*Any update to below level should update g_strTraceLevelName as well.*/
#define 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; \
  for(i=0; i<n; i++) printf("%s[%d]: %d\n", _STR(tlist), i, (int)tlist[i]); \
}while(0)



#ifdef __cplusplus
}
#endif


#endif /*__TRACE_API_H__*/
View Code

 

首先,是幾個判斷當前運行狀態的函數,shell,kernal,interrupt三種,若是這三種狀態都不是,那就是應用程序。緊接着的,是z_Log2xxx函數,用來作最終輸出的,包括console,file,memory等多個選項。而後是z_xxxLog/z_ShellPrintxxx函數,這個是封裝前的幾個打印函數,他們會調用前面的z_Log2xxx。再往下,是一組互斥量操做,內存日誌支持函數,以及日誌打印級別定義和操做。

對於通常使用者來講,這些都不須要理解,由於它們對用戶是透明的。

輸出級別定義後,是一些相對具體的日誌輸出接口,是些宏定義。這裏有選擇的講一講。

#define VERBOSE_LINE(fnPrint, level) fnPrint("[%s]:%s %s %d::", _STR(level), __FILE__, __FUNCTION__, __LINE__)

定義額外調試信息,咱們不想老是去敲入__FUNCTION__,__LINE__,用這個宏定義來解決。

zTraceV和zTraceQ是全部程序日誌使用的;zTraceP和zTracePV則是用來給SHELL用的,且只用在明確的提示信息輸出,好比在ShowTaskName()函數裏使用。SHELL的運行錯誤等內容,一樣是調用zTraceV或是zTraceQ。TraceHex是個額外的封裝,按16進制方式,輸出buff內容。這裏的V表示包含剛剛那個verbose調試信息,而Q則表示不包含。

zTraceV和zTraceQ都須要提供level。之前哥是把它們作成函數的,但後來發現,有時候用戶會傳進非法的level;再就是,函數會形成沒必要要的開銷,即使level判斷不須要打印,也會形成至關大的運行開銷。所以,如今它們被改爲了宏,如此一來,level判斷在生成打印函數的堆棧信息前,就作了判斷,避免了沒必要要的堆棧變化。

對於使用者來講,這些也都仍是透明的。只有再下面的定義,纔是他們要關心和使用的。哥給出的zTrace,分5個級別:zTraceFatal,zTraceAlarm,zTraceError,zTraceWarn,zTraceInfo,和zTraceDebug。針對不一樣的需求,有分爲V,Q,Hex,HexQ四個類別。編程的時候,大約95%的狀況下,咱們只用那5個基本接口(默認Verbose模式)。
下面是個樣例:

int   DataBlockFreeCount(void* pool)
{
  zDataPool_t *pPool = (zDataPool_t *)pool;
  if(!pool)
  {
    zTraceWarn("Bad input null pool.\n");
    return -1;
  }

  return pPool->freeCount;
}

注意了,level是個bit位,每一個level都是獨立的。這裏哥用的是warn,而不是其它level。建議是,系統初始化代碼裏,多用fatal,由於那會出錯了就意味着系統無法運行;alarm級別多用在系統性錯誤,表示系統已經無法服務了;error級別則不要隨便用於運行錯誤,只能用於編碼錯誤;warn級別用於業務或是通常程序運行錯誤;info級別專供輸出主要業務邏輯,好比接口消息的輸出,用於定位業務故障;debug級別就是開放給程序員用的,想用就用吧!

info級別其實能夠被擴展成msginfo和runinfo的,那樣可能更好用。另外,level能夠定義成32bits的,那樣的話,應用程序能夠把info級別擴展成若干個feature相關的level。

在程序設計中,除了專供中斷等須要頻繁調用高效處理場合使用的代碼,zTrace沒什麼限制。並且,zTrace不須要使用前作初始化(g_zTraceLock編譯初始化成unlock),所以程序能夠在任什麼時候間使用。

如何作到讓zTrace根據模塊或是線程來定製日誌輸出級別?在zTrace編程你們看到的樣子前,我是用模塊號來定製輸出的。但那麼作,就須要在zTrace的參數裏提供模塊號。這麼作挺好的,能夠把一個任務切割成幾個不一樣的功能模塊來定製日誌級別;缺點是,不少函數是公用的,並且應用中,經常看到打印後,殊不知道究竟是哪一個任務打印的。因而,慢慢的,就演進到如今的樣子:根據線程來決定級別。

爲避免沒必要要的CPU開銷,zTrace首先判斷是否使用common的level配置;只有在調試狀態(這時候,性能再也不重要),纔會出現定製的level。固然,針對線程的定製level也並非那麼可怕,只不過微微多佔用點CPU罷了。

在結束前,哥建議使用者多利用zTraceLevelSet函數。好比,之前我作電話交換系統,會在消息入口處,對將主、被叫電話號碼同跟蹤列表進行比對,當發現須要跟蹤時,會使用zTraceLevelSet置日誌info級別,而處理完消息後,則重置成默認級別。以前我講狀態機編程的時候,提到了FsmTrace。在使用中,FsmTrace同樣須要選擇性的trace,那是經過traceId來控制的。

好了,差很少講完了。zTrace雖好,但仍需配合其它手段使用,好比EOS等,毫不可偏廢。作任何系統,都應該目標「5分鐘找到問題」,「不必重現故障」。固然,開發工做量也至少會多出一倍。

相關文章
相關標籤/搜索