acl 日誌記錄方式介紹

      在使用 acl 庫編寫應用過程當中,記錄日誌是一個很是重要的過程,acl 從幾個層面提供了日誌的不一樣記錄方式。在 acl 的 C 庫部分(lib_acl.a),有三個源文件與日誌記錄相關:acl_msg.c/acl_msg.h, acl_mylog.c/acl_mylog.h, acl_debug.c/acl_debug.h。其中,acl_mylog.c 是真正記錄日誌的源文件,acl_msg.c 則是在 acl_mylog.c 基礎之上的二次封裝,acl_debug.c 是在 acl_msg.c 基礎之上的再次封裝。下面根據此三個日誌源文件從三個層次描述日誌記錄的過程。安全

 

      1、ac_mylog.c/acl_mylog.h網絡

      打開 acl_mylog.h 頭文件,能夠看到主要有三個函數:acl_open_log(打開日誌文件),acl_write_to_log(寫日誌)以及acl_close_log(關閉日誌)---(這三個函數是最基礎的日誌記錄過程,固然咱們沒必要直接使用)。該庫支持兩類日誌記錄方式:一、本地文件記錄方式,二、與 syslog-ng 結合的網絡日誌記錄方式。本地文件記錄方式是 acl 日誌庫對外提供的最簡單的日誌記錄方式,此方式不依賴於第三方日誌庫,但不該用在生產環境中,由於該方式不支持日誌回滾等高級特性,爲了便於生產上使用,因此產生了第二種方式(與 syslog-ng 結合),查看日誌打開接口(以下):svn

/**
 * 打開日誌文件
 * @param recipients {const char*} 日誌接收器列表,由 "|" 分隔,接收器
 *  能夠是本地文件或遠程套接口,如:
 *  /tmp/test.log|UDP:127.0.0.1:12345|TCP:127.0.0.1:12345|UNIX:/tmp/test.sock
 *  該配置要求將全部日誌同時發給 /tmp/test.log, UDP:127.0.0.1:12345,
 *  TCP:127.0.0.1:12345 和 UNIX:/tmp/test.sock 四個日誌接收器對象
 * @param plog_pre {const char*} 日誌記錄信息前的提示信息,建議用進程
 *  名填寫此值
 */
ACL_API int acl_open_log(const char *recipients, const char *plog_pre);

       從上面的函數聲明能夠看出,acl 的日誌記錄容許同時輸出至多個日誌管道中(最簡單的方式就是直接寫入本地磁盤文件:/tmp/test.log),同時更應看到,其中有三個奇怪的日誌文件表達方式:UDP:IP:PORT, TCP:IP:PORT, UNIX:/xxx,其實這三種方式均是與 syslog-ng 相關,即分別表示:函數

      一、以 UDP 方式發送日誌至 syslog-ng;ui

      二、以 TCP 方式發送日誌至 syslog-ng;.net

      三、以 UNIX 域套接字方式發送日誌至 syslog-ng。
      由於日誌管理是一個很是複雜的過程,因此在 acl 除了提供最簡單的日誌文件記錄外,更建議用戶將日誌輸出至 syslog-ng 中(做者本身的項目也每每是這樣作的)。線程

 

      2、acl_msg.c/acl_msg.hdebug

      該日誌庫提供了更爲高級的日誌記錄方法,不只提供了靈活的日誌記錄函數,同時還容許用戶註冊本身的日誌記錄函數庫,該日誌庫主要函數接口以下:調試

/**
 * 日誌打開函數
 * @param log_file {const char*} 日誌接收者集合,由 "|" 分隔,接收器
 *  能夠是本地文件或遠程套接口,如:
 *  /tmp/test.log|UDP:127.0.0.1:12345|TCP:127.0.0.1:12345|UNIX:/tmp/test.sock
 *  該配置要求將全部日誌同時發給 /tmp/test.log, UDP:127.0.0.1:12345,
 *  TCP:127.0.0.1:12345 和 UNIX:/tmp/test.sock 四個日誌接收器對象
 * @param plog_pre {const char*} 日誌記錄信息前的提示信息,建議用進程
 * @param info_pre {const char*} 日誌記錄信息前的提示信息
 */
ACL_API void acl_msg_open(const char *log_file, const char *info_pre);

/**
 * 關閉日誌函數
 */
ACL_API void acl_msg_close(void);

       上面是日誌打開與關閉的函數,看上去算是相對簡單。下面是幾個日誌記錄的函數接口:日誌

/**
 * 通常級別日誌信息記錄函數
 * @param fmt {const char*} 參數格式
 * @param ... 變參序列
 */
#ifdef	WIN32
ACL_API void acl_msg_info(const char *fmt,...);
#else
ACL_API void __attribute__((format(printf,1,2)))
	acl_msg_info(const char *fmt,...);
#endif

/**
 * 警告級別日誌信息記錄函數
 * @param fmt {const char*} 參數格式
 * @param ... 變參序列
 */
#ifdef	WIN32
ACL_API void acl_msg_warn(const char *fmt,...);
#else
ACL_API void __attribute__((format(printf,1,2)))
	acl_msg_warn(const char *fmt,...);
#endif

/**
 * 錯誤級別日誌信息記錄函數
 * @param fmt {const char*} 參數格式
 * @param ... 變參序列
 */
#ifdef	WIN32
ACL_API void acl_msg_error(const char *fmt,...);
#else
ACL_API void __attribute__((format(printf,1,2)))
	acl_msg_error(const char *fmt,...);
#endif

/**
 * 致命級別日誌信息記錄函數
 * @param fmt {const char*} 參數格式
 * @param ... 變參序列
 */
#ifdef	WIN32
ACL_API void acl_msg_fatal(const char *fmt,...);
#else
ACL_API void __attribute__((format(printf,1,2)))
	acl_msg_fatal(const char *fmt,...);
#endif

/**
 * 恐慌級別日誌信息記錄函數
 * @param fmt {const char*} 參數格式
 * @param ... 變參序列
 */
#ifdef	WIN32
ACL_API void acl_msg_panic(const char *fmt,...);
#else
ACL_API void __attribute__((format(printf,1,2)))
	acl_msg_panic(const char *fmt,...);
#endif

       能夠看到,這些函數的使用方式與 printf 相似,另外,在 UNIX 下使用 GCC 編譯時前面還有一個修飾符:__attribute__((format(printf,m,n))),這主要是方便 gcc 編譯器針對變參進行語法檢查(你們應該知道變參是如此方便靈活而又如此容易出錯)。

      爲了方便程序開發過程當中的調試,下面的函數當用戶未調用 acl_msg_open 打開日誌而直接使用 acl_msg_xxx 寫日誌時,決定是否將日誌信息輸出至屏幕(這個函數應該在程序初始化時調用):

/**
 * 當未調用 acl_msg_open 方式打開日誌時,調用了 acl_msg_info/error/fatal/warn
 * 的操做,是否容許信息輸出至標準輸出屏幕上,經過此函數來設置該開關,該開關
 * 僅影響是否須要將信息輸出至終端屏幕而不影響是否輸出至文件中
 * @param onoff {int} 非 0 表示容許輸出至屏幕
 */
ACL_API void acl_msg_stdout_enable(int onoff);

       前面曾說過,acl 的日誌庫還容許用戶使用本身的日誌記錄過程,但要求用戶必須在程序初始化時註冊本身的日誌處理函數,以下:

/**
 * 在打開日誌前調用此函數註冊應用本身的日誌打開函數、日誌關閉函數、日誌記錄函數
 * @param open_fn {ACL_MSG_OPEN_FN} 自定義日誌打開函數
 * @param close_fn {ACL_MSG_CLOSE_FN} 自定義日誌關閉函數
 * @param write_fn {ACL_MSG_WRITE_FN} 自定義日誌記錄函數
 * @param ctx {void*} 自定義參數
 */
ACL_API void acl_msg_register(ACL_MSG_OPEN_FN open_fn, ACL_MSG_CLOSE_FN close_fn,
        ACL_MSG_WRITE_FN write_fn, void *ctx);

     調用此函數後,之後的日誌記錄過程(即當用戶調用:acl_msg_xxx 相關過程時)的內容便輸出便由用戶的日誌庫控制。

 

      除了以上主要的日誌函數接口,在 acl_msg 中還提供瞭如下幾個函數,便於用戶知曉程序出錯緣由:

/**
 * 得到上次系統調用出錯時的錯誤描述信息,該函數內部採用了線程局部變量,因此是線程
 * 安全的,但使用起來更簡單些
 * @return {const char *} 返回錯誤提示信息 
 */
ACL_API const char *acl_last_serror(void);

/**
 * 得到上次系統調用出錯時的錯誤號
 * @return {int} 錯誤號
 */
ACL_API int acl_last_error(void);

 

      3、acl_debug.c/acl_debug.h

      該日誌函數庫是在 acl_msg 之上的再一次封裝,該庫的思想來源於 squid 的日誌記錄方式,能夠將日誌分紅不一樣的類別,每個類別又分紅不一樣的級別,這樣用戶就能夠很是方便地經過配置文件來記錄不一樣類別的不一樣級別的日誌信息了。在程序初始化時需先調用如此函數:

/**
 * 初始化日誌調試調用接口
 * @param pStr {const char*} 調試類別(建議值在100至1000之間)標籤及級別字符串,
 *  格式: 1,1; 2,10; 3,8...  or 1:1; 2:10; 3:8...
 */
ACL_API void acl_debug_init(const char *pStr);

/**
 * 初始化日誌調試調用接口
 * @param pStr {const char*} 調試標籤及級別字符串,
 *  格式: 1,1; 2,10; 3,8...  or 1:1; 2:10; 3:8...
 * @param max_debug_level {int} 最大調試標籤值
 */
ACL_API void acl_debug_init2(const char *pStr, int max_debug_level);

       其中,第一個參數是一個由日誌記錄類別與級別組成的字符串,格式爲:類別1:最大記錄級別, 類別2:最大記錄級別, ...。例如:100:2; 102:3; 103:4,其含義是日誌將會記錄類別爲 100 的全部級別值小於二、類別爲 101 的全部級別值小於 3 以及類別爲 103 的全部級別值小於 4 的日誌信息。關於記錄類別須要注意:類別值最好是 >= 100,且 < 1000(當使用 acl_debug_init2 初始化時只要類別值 >= 100 便可,由於第二個參數指定了最大類別值),這是由於 acl 庫內部一些保留的類別值都在 0 -- 100 之間。

       那麼具體的使用這些類別與級別記錄日誌的接口是什麼呢?以下所示:

/**
 * 日誌調試宏接口
 * @param SECTION {int} 調試標籤值
 * @param LEVEL {int} 對應於SECTION調試標籤的級別
 */
#define acl_debug(SECTION, LEVEL) \
	!acl_do_debug((SECTION), (LEVEL)) ? (void) 0 : acl_msg_info

        看到了吧,用戶其實只須要調用一個宏便可,以下面的例子: 

/* 初始化日誌類別記錄 */
	const char *str = "101:2; 103:4; 105:3";
	/* 記錄全部類別值爲 101 級別小於等於 二、類別值爲 102 級別小於等於 四、類別值爲 105 級別小於等於 3 的日誌內容 */
	acl_debug_init(str);

	......
	/* 下面的日誌因符合類別值 101 級別值 <= 2 而被記錄 */
	acl_debug(101, 2)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));

	/* 下面日誌符合類別 105 的記錄級別 */
	acl_debug(105, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));

	/* 下面的日誌因不符合類別值 103 的記錄級別條件而被忽略 */
	acl_debug(103, 5)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));

	/* 下面日誌的類別值 102 因不存在而被忽略 */
	acl_debug(102, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));

 

      此外,爲了方便,還能夠傳給 acl_debug_init 的參數寫爲:"all:1",意思是全部類別的級別值 <= 1 的日誌都將被記錄,以下面的內容都會被記錄:

 

acl_debug_init("all:1");
......
	acl_debug(100, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
	acl_debug(101, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
	acl_debug(101, 0)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
	acl_debug(102, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
	acl_debug(103, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
	acl_debug(104, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
	acl_debug(105, 1)("%s(%d): log time: %ld", __FILE__, __LINE__, time(NULL));
        ......

 

       ok,有關日誌  acl 日誌記錄函數就先寫這些,使用者能夠根據項目須要採用不一樣的日誌記錄方式。

 

      參考:

      本文地址:http://zsxxsz.iteye.com/blog/1893115

      更多文章:http://zsxxsz.iteye.com/

      下載:http://sourceforge.net/projects/acl/

      svn:svn checkout svn://svn.code.sf.net/p/acl/code/trunk acl-code

      QQ 羣:242722074

相關文章
相關標籤/搜索