乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      高效可靠的日志記錄模塊的構(gòu)建 | 艾瑞楓∑°

       青松卓然 2012-08-09

      高效可靠的日志記錄模塊的構(gòu)建

        作為軟件開發(fā)者,要記錄軟件日志是再平常不過(guò)的事兒了————就跟吃家常便飯一樣,比如:日志是后臺(tái)服務(wù)進(jìn)程能唯一發(fā)出抱怨的地方(不信可以看看 ls /var/log),可以用日志記錄用戶的行為及操作,還可以用日志記錄軟件自身的行為以便用于調(diào)試(見下一篇文章《基于日志的內(nèi)存泄露的檢測(cè)方法》就是利用這個(gè)哈)。既然這么多地方都用到了日志,那么作為一個(gè)崇尚自由的程序員,就不能重復(fù)造輪子———至少自己不能。所以必須要?jiǎng)?chuàng)建一個(gè)可移植的,簡(jiǎn)便高效的日志模塊。

        第一步要確定日志的格式,雖然各種風(fēng)格的日志格式各異,但我比較喜歡linux風(fēng)格的日志記錄,命令vim /var/log/kern.log,可以看到日志內(nèi)容被高亮顯示了:



        其中每行的開頭是記錄此條記錄時(shí)的時(shí)間,這里是7月7日16時(shí)45分01秒,ulinux是主機(jī)名,CRON[25642]是記錄進(jìn)程的名字及PID(這里是內(nèi)核),最后是日志內(nèi)容。Linux日志記錄根據(jù)重要性還分為從DEBUG到EMERG的若干等級(jí),標(biāo)識(shí)該消息的重要性。在Linux下編程可以用syslog函數(shù)向系統(tǒng)日志(/var/log/syslog)中寫入信息,syslog函數(shù)的用法如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
       
      /* 
       * ident字符串會(huì)被附加到每一條用syslog寫入的條目前面(可為NULL),
       * option指定一些可選項(xiàng)(如是否記錄PID),falicity用于指定該進(jìn)程的類型(如FTP是LOG_FTP)
       */
      void openlog(const char *ident, int option, int facility); 
       
      /*
       * priority指定消息的等級(jí),有LOG_EMERG/ALERT/CRIT/ERR/WARNING/NOTICE/INFO/DEBUG
       * 等8個(gè)等級(jí),越往后的等級(jí)越不重要
       */
      void syslog(int priority, const char*format, ...);
       
      void closelog(void);

        Linux的日志設(shè)計(jì)從輸出格式到函數(shù)用法都很有借鑒意義,但又考慮到此日志可能會(huì)作為軟件調(diào)試日志來(lái)用,因此還需要附上寫此條記錄的代碼所在的文件名、函數(shù)名、行號(hào)等信息,進(jìn)程名和PID顯然不需要了,在此用[]來(lái)存放等級(jí)信息,然后再用{}來(lái)存放代碼位置信息?,F(xiàn)在我們定義的日志格式就成了這個(gè)樣子了: 06/Jul/2012 22:28:18 [Debug] {test.c main 18} 記錄內(nèi)容… … ??瓷先バU順眼的,就這么定了吧。

        現(xiàn)在已經(jīng)設(shè)計(jì)完畢,該去實(shí)現(xiàn)它了。因?yàn)檫@些代碼將來(lái)還不知道在什么環(huán)境下運(yùn)行,所以在實(shí)現(xiàn)過(guò)程中要考慮到很多內(nèi)容:

        1、可移植性。使用C語(yǔ)言編寫,因此各種平臺(tái)下的各種C/C++項(xiàng)目都可以用。
        2、便捷性。向開發(fā)人員提供便捷的操作接口,只需一行代碼就可以寫入日志。
        3、可靠性。需要考慮到多線程的情況,必須設(shè)計(jì)成線程安全的。
        4、靈活性。提供稍微復(fù)雜一點(diǎn)的接口以完成更“個(gè)性化”的操作。
        5、支持多個(gè)日志。默認(rèn)可以同時(shí)打開100個(gè)日志文件,此值可根據(jù)需要調(diào)整。
        6、支持系統(tǒng)日志。開發(fā)人員除了那100個(gè)可以使用的日志外,還存在一個(gè)特殊的“系統(tǒng)日志”,用于跟蹤內(nèi)存分配/釋放操作,主要由內(nèi)存調(diào)試模塊用來(lái)分析內(nèi)存泄漏等(見下一篇文章《基于日志的內(nèi)存泄露的檢測(cè)方法》),但是也允許開發(fā)人員使用。

        根據(jù)這些準(zhǔn)則,首先把數(shù)據(jù)結(jié)構(gòu)和用戶接口實(shí)現(xiàn)了:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      
      #define MAX_LOGS		100					/* 用戶最多可打開日志數(shù) */
       
      #define LOG_INVALID		-1					/* 無(wú)效的日志描述符(初始化定義) */
      #define LOG_SYSTEM		 0					/* 系統(tǒng)日志(xmalloc, xabort.etc) */
      #define LOG_USER		 1					/* 分配給用戶的最小日志描述符 */
       
      enum{
      	LOG_LEVEL_EMERG = 0,						/* System is unusable */
      	LOG_LEVEL_ALERT,						/* Action must be taken immediately */
      	LOG_LEVEL_CRIT,							/* Critical conditions */
      	LOG_LEVEL_ERROR,						/* Error conditions */
      	LOG_LEVEL_WARNING,						/* Warning conditions */
      	LOG_LEVEL_NOTICE,						/* Normal, but significant */
      	LOG_LEVEL_INFO,							/* Information */
      	LOG_LEVEL_DEBUG,						/* DEBUG message */
      };
       
      int	log_open(const char *path, const char* mode);			/* 打開用戶日志文件 */
      int	log_open_sys(const char *path, const char* mode);		/* 打開系統(tǒng)日志文件 */
       
      void	log_printf_raw(int log_id, const char *fmt, ...);		/* 無(wú)日期、等級(jí)、位置信息 */
      void	log_printf0(int log_id, int level, const char *fmt, ...);	/* 無(wú)位置信息 */
       
      void	log_flush(int log_id);						/* 將緩沖區(qū)數(shù)據(jù)寫入日志 */
      void	log_flush_sys();
       
      void	log_close(int log_id);						/* 關(guān)閉日志文件 */
      void	log_close_sys();
      void	log_close_all();



        用戶使用log_open來(lái)打開日志(參數(shù)跟fopen一樣),然后就可以調(diào)用log_printf_raw和log_printf0來(lái)向日志中添加記錄了。這兩個(gè)函數(shù)的第一個(gè)參數(shù)都是log_open返回的描述符,兩者不同的是前者僅寫入用戶指定的內(nèi)容,而后者則會(huì)在每條記錄的開頭添加時(shí)間信息、重要等級(jí)信息。但是遺憾的是,兩者都沒有記錄代碼位置信息(文件名、函數(shù)名、行號(hào)等),這是因?yàn)橐韵聝蓚€(gè)原因:1、再加上行號(hào)會(huì)導(dǎo)致函數(shù)的參數(shù)太多,易用性不強(qiáng)。2、用戶每調(diào)用這個(gè)函數(shù)都要鍵入文件名等信息,累不說(shuō),代碼也會(huì)不美觀。log_printf就是為了解決這個(gè)問(wèn)題的,但是它的定義要頗費(fèi)一番周折,現(xiàn)在要一步步分析它產(chǎn)生的過(guò)程。

        接下來(lái)就要談到宏的高級(jí)特性了。首先,有幾個(gè)特殊的宏是專門用來(lái)記錄該行代碼的文件名、函數(shù)名和行號(hào)信息的,它們是__FILE__, __FUNCTION__, __LINE__,這好像方便了一些,因?yàn)檎{(diào)用函數(shù)的時(shí)候不用先看一下文件名、函數(shù)名再數(shù)一下行號(hào)了,但是這還是不理想,還是累??!怎么辦呢?其實(shí)我們可以換個(gè)思路,當(dāng)用戶要調(diào)用寫入函數(shù)的時(shí)候,自動(dòng)地給它加上這些信息多好!于是這需要用到宏的一般替換功能,比如這樣定義:

      #define log_printf(x) log_printf0(x, __FILE__, __FUNCTION__, __LINE__)

        于是當(dāng)用戶調(diào)用log_printf的時(shí)候,行號(hào)等信息就被自動(dòng)加上了!但是,別高興地太早了,咱們這里的log_printf0有個(gè)可變參數(shù)”…”,要怎么替換它?沒轍。而且這樣的話還要修改log_printf0的定義,破壞了原來(lái)設(shè)計(jì)的接口。
        是不是真沒辦法了?NO。您一定知道有可變參數(shù)函數(shù),但您知道有可變參數(shù)紅這種東西嗎?如果不知道,那就真沒轍了。算起來(lái)這也算宏的高級(jí)特性了,最新的GCC和VS才開始支持??勺儏?shù)宏就是為了解決這種情況而生的! 它用__VA_ARGS__來(lái)代表所有可變的參數(shù)部分。于是,我們的定義變成了這樣:

      #define log_printf(id, level, fmt, …) log_printf0(id, level, __FILE__, __FUNCTION__, __LINE__, fmt, __VA_ARGS__)

        現(xiàn)在目標(biāo)大體實(shí)現(xiàn)了,而且編譯通過(guò)。但是這還是改變了log_printf0的接口啊,以后調(diào)用太復(fù)雜了!別急,宏的威力還不止如此。您是否知道宏的連接特性?用##可以連接起兩個(gè)字符串來(lái),就像”AB”##”CD”,在預(yù)處理之后就會(huì)變成”ABCD”了!而我們的fmt參數(shù)又是一個(gè)形如”%s %d”樣式的字符串,我們?cè)俳o它加上幾個(gè)參數(shù)又如何!哈哈,感覺到希望了!

      #define log_printf(id, level, fmt, …) log_printf0(id, level, “{%s %s %d} “##fmt, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS)

        現(xiàn)在,如果我們調(diào)用 log_printf(id, LOG_DEBUG, “%s”, “內(nèi)容”),它就會(huì)被展開為 log_printf0(id, LOG_DEBUG, “{%s %s %d} %s”, __FILE__, __FUNCTION__, __LINE__ , “內(nèi)容”)

        看似大功告成!但是其實(shí)還不夠完善,
        首先,因?yàn)開_FILE__是全路徑名,會(huì)導(dǎo)致日志臃腫;
        其次,當(dāng)用戶沒有傳入可變參數(shù)的時(shí)候(比如fmt是一個(gè)純字符串),在展開后__LINE__后面還有一個(gè)多余的逗號(hào)。當(dāng)然可以告訴開發(fā)者一定要傳個(gè)參數(shù)進(jìn)來(lái),但這太不友好了。其實(shí)編譯器開發(fā)者們也考慮到了這個(gè)問(wèn)題,解決方法是在__VA_ARGS__前加上##(這里不是字符串連接作用哦);
        最后,在給用戶看的日志中,可能不希望透漏代碼位置信息;可以的解決方案是在DEBUG模式下生成代碼位置信息,而Release模式下不生成此信息,宏DBG_LOGTRACE控制此特性。

      這是log_printf的最終版:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      #ifdef DBG_LOGTRACE
        #define log_printf(id, level, fmt, ...)			 		log_printf0(id, level, "{%s %s %d} "##fmt,   		path_find_file_name(__FILE__), __FUNCTION__, 		__LINE__, ##__VA_ARGS__)
      #else
        #define log_printf(id, level, fmt, ...)			 		log_printf0(id, level, fmt, ##__VA_ARGS__)
      #endif

      一個(gè)完整的日志使用示例如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
       
      int f1 = log_open(path, "w");   /* 軟件啟動(dòng)時(shí)或使用前 */
      ...
       
      log_printf(f1, LOG_DEBUG, "現(xiàn)在%s的值是%d", "a", 100);
      log_printf(f1, LOG_DEBUG, "%s測(cè)試", "bb");
       
      int f2 = log_open(path2, "a");  /* 又要寫另一個(gè)日志了 */
      log_printf (f2, LOG_INFO, "另一個(gè)日志");
      log_close(f2);
      ...
      log_close(f1);   /* 軟件推出前或不再使用日志后 */

      生成的第一個(gè)LOG內(nèi)容如下:

      06/Jul/2012 22:28:26 [Debug] {iri.c func1 144} 現(xiàn)在a的值是100
      06/Jul/2012 22:28:26 [Debug] {iri.c func2 721} bb測(cè)試

      日志格式完全滿足調(diào)試要求了。接下來(lái)看看具體的實(shí)現(xiàn)。使用靜態(tài)全部變量數(shù)據(jù)來(lái)存放文件指針,再聲明一個(gè)同樣大的數(shù)組來(lái)存放對(duì)應(yīng)的自旋鎖。
      自旋鎖用來(lái)實(shí)現(xiàn)線程間的互斥,如果不明白可以參見前一篇文件《線程同步與互斥——可移植的用戶空間自旋鎖》。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      
      /* 日志記錄 */
      struct log_level_desc{
      	enum LOG_LEVEL	level;
      	char*			endesc;
      	wchar_t*		cndesc;
      };
       
      static struct log_level_desc log_level_descs[] = {
      	{ LOG_LEVEL_EMERG,	"Emergency",		L"致命" },
      	{ LOG_LEVEL_ALERT,	"Alert",		L"危急" },
      	{ LOG_LEVEL_CRIT,	"Critical",		L"緊急" },
      	{ LOG_LEVEL_ERROR,	"Error",		L"錯(cuò)誤" },
      	{ LOG_LEVEL_WARNING,	"Warning",		L"警告" },
      	{ LOG_LEVEL_NOTICE,	"Notice",		L"注意" },
      	{ LOG_LEVEL_INFO,	"Info",			L"消息" },
      	{ LOG_LEVEL_DEBUG,	"Debug",		L"調(diào)試" },
      };
       
      static FILE* log_files[MAX_LOGS+1];
      static spin_lock_t log_locks[MAX_LOGS+1];
       
      /* 打開用戶日志文件 */
      int	log_open(const char *path, const char* mode)
      {
      	int i;
       
      	for (i = LOG_USER; i < MAX_LOGS; i++)
      	{
      		if (log_files[i] == NULL)
      		{
      			log_files[i] = fopen(path, mode);
       
      			if (!log_files[i])
      				return LOG_INVALID;
       
      			spin_init(&log_locks[i], NULL);
      			return i;
      		}
      	}
       
      	return LOG_INVALID;
      }
       
      /* 打開系統(tǒng)日志文件 */
      int log_open_sys(const char *path, const char* mode)
      {
      	if (log_files[LOG_SYSTEM])
      		return LOG_SYSTEM;
       
      	log_files[LOG_SYSTEM] = fopen(path, mode);
      	if (!log_files[LOG_SYSTEM])
      		return LOG_INVALID;
       
      	spin_init(&log_locks[LOG_SYSTEM], NULL);
      	return LOG_SYSTEM;
      }
       
      /* 寫入日志文件 */
      void log_printf0(int log_id, int log_level, const char *fmt, ...)
      {
      	FILE *fp;
      	time_t t;
      	char tmbuf[30];
      	const char *p;
      	va_list args;
      	int level;
       
      	if (log_id < 0 || log_id >= MAX_LOGS)
      		return;
      	else if (!(fp = log_files[log_id]))
      		return;
       
      	spin_lock(&log_locks[log_id]);
       
      	if (log_level > LOG_LEVEL_DEBUG)
      		level = LOG_LEVEL_DEBUG;
      	else if (log_level < LOG_LEVEL_EMERG)
      		level = LOG_LEVEL_EMERG;
      	else
      		level = log_level;
       
      	//時(shí)間信息
      	t = time(NULL);
      	memset(tmbuf, 0, sizeof(tmbuf));
      	strftime(tmbuf, sizeof(tmbuf), "%d/%b/%Y %H:%M:%S", localtime(&t));
      	fprintf (fp, "%s ", tmbuf);
       
      	//等級(jí)信息
      	fprintf(fp, "[%s] ", log_level_descs[level].endesc);
       
      	//正文信息
      	va_start(args, fmt);
      	vfprintf(fp, fmt, args);
      	va_end(args);
       
      	//換行符
      	p = fmt + strlen(fmt) - 1;
      	if (*p != '\n')
      		fputc('\n', fp);
       
      	spin_unlock(&log_locks[log_id]);
      }

      最后貼上一個(gè)現(xiàn)實(shí)項(xiàng)目中的調(diào)試日志內(nèi)容:

      View Code PLAIN
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      
      06/Jul/2012 21:46:42 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 190} =======================================
      06/Jul/2012 21:46:42 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 191} Start download url http://www./wp-content/plugins/nextgen-gallery/js/jquery.cycle.all.min.js?ver=2.9995.
      06/Jul/2012 21:46:42 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 192} Saving to E:\Projects\SafeSite\Debug\www.+80.5\wp-content\plugins\nextgen-gallery\js\jquery@ver=2.9995.cycle.all.min.js.
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 264} Mark this downloaded url as done.
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 348} Thread finished normally.
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 190} =======================================
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 191} Start download url http://www./wp-content/plugins/multi-level-navigation-plugin/scripts/superfish.js?ver=1.4.8.
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 192} Saving to E:\Projects\SafeSite\Debug\www.+80.5\wp-content\plugins\multi-level-navigation-plugin\scripts\superfish@ver=1.4.8.js.
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 264} Mark this downloaded url as done.
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 348} Thread finished normally.
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 190} =======================================
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 191} Start download url http://www./xmlrpc.php?rsd.
      06/Jul/2012 21:46:43 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 192} Saving to E:\Projects\SafeSite\Debug\www.+80.5\xmlrpc@rsd.php.
      06/Jul/2012 21:46:44 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 264} Mark this downloaded url as done.
      06/Jul/2012 21:46:44 [Info] {retrthread.cpp CRetrThread::_GetURLDataBycurl 348} Thread finished normally.



      呵呵,這才算是大功告成了!
      短短幾百行代碼,以后就能一勞永逸了 ~\(≧▽≦)/~

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多