天天看點

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:

出處: http://blog.csdn.net/luotuo44/article/details/38317797

日志處理:

        在Libevent的源碼中,經常會見到形如event_warn、event_msgx、event_err之類的函數。這通常出現在代碼中一些值是不合理時。這些函數就是Libevent的日志函數。它能把這些不合理的情況列印出來,告知使用者。

定制日志回調函數:

        Libevent在預設情況下,會将這些日志資訊輸出到終端上。這當然就不利于日後的觀察。為此,Libevent允許使用者定制自己的日志回調函數。所有的日志函數在最後輸出資訊時,都會調用日志回調函數的。是以使用者可以通過定制自己的日志回調函數(在回調函數中把資訊輸出到一個檔案上),友善日後的檢視。定制回調函數就像設定自己信号處理函數那樣,設定一個日志回調函數。當有日志時,Libevent庫就會調用這個日志回調函數。

        回調函數的格式和日志定制函數如下所示:

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. typedef void (*event_log_cb)(int severity, const char *msg);  
  2. void event_set_log_callback(event_log_cb cb);  

        回調函數中的第一個參數severity是日志級别類型,有下面這些:

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. #define EVENT_LOG_DEBUG 0  
  2. #define EVENT_LOG_MSG   1  
  3. #define EVENT_LOG_WARN  2  
  4. #define EVENT_LOG_ERR   3  
  5. #define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG  
  6. #define _EVENT_LOG_MSG EVENT_LOG_MSG  
  7. #define _EVENT_LOG_WARN EVENT_LOG_WARN  
  8. #define _EVENT_LOG_ERR EVENT_LOG_ERR  

        值得注意的是,不能在你的日志回調函數裡面調用任何Libevent提供的API函數,否則将發生未定義行為。

        在實作上,Libevent是通過定義一個全局函數指針變量來儲存使用者在日志定制函數中傳入的參數cb。日志定制函數在實作上也是很簡單的

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. static event_log_cb log_fn = NULL;  
  2. void  
  3. event_set_log_callback(event_log_cb cb)  
  4. {  
  5.       log_fn = cb;  
  6. }  

        從event_set_log_callback的實作代碼可以看到,并沒有對這個參數cb做任何檢查。

        Libevent的預設日志處理函數event_log函數還是很簡陋的,隻是簡單地根據參數判斷日志記錄的級别,然後把級别和日志内容輸出。複雜一點的日志功能,可以參考muduo日志功能。當然也有log4pp、log4xx這些把一個重量級的日志庫。

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. static void  
  2. event_log(int severity, const char *msg)  
  3. {  
  4.     if (log_fn)  
  5.         log_fn(severity, msg);//調用使用者的日志回調函數  
  6.     else {  
  7.         const char *severity_str;  
  8.         switch (severity) {  
  9.         case _EVENT_LOG_DEBUG:  
  10.             severity_str = "debug";  
  11.             break;  
  12.         case _EVENT_LOG_MSG:  
  13.             severity_str = "msg";  
  14.             break;  
  15.         case _EVENT_LOG_WARN:  
  16.             severity_str = "warn";  
  17.             break;  
  18.         case _EVENT_LOG_ERR:  
  19.             severity_str = "err";  
  20.             break;  
  21.         default:  
  22.             severity_str = "???";  
  23.             break;  
  24.         }  
  25.         (void)fprintf(stderr, "[%s] %s\n", severity_str, msg);//輸出到标準錯誤終端上  
  26.     }  
  27. }  

        從event_log函數中可以看到,當函數指針log_fn不用NULL時,就調用log_fn指向的函數。否則就直接向stderr輸出日志資訊。是以,設定自己的日志回調函數後,如果想恢複Libevent預設的日志回調函數,隻需再次調用event_set_log_callback函數,參數設定為NULL即可。

日志API以及日志消息處理流程:

        Libevent的日志API的使用也是挺簡單的。首先,使用者确定要記錄的日志的級别和錯誤類型,然後調用對應的日志函數。有下面這些可供選擇的日志函數。

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. void event_err(int eval, const char *fmt, ...);  
  2. void event_warn(const char *fmt, ...);  
  3. void event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...);  
  4. void event_sock_warn(evutil_socket_t sock, const char *fmt, ...);  
  5. void event_errx(int eval, const char *fmt, ...);  
  6. void event_warnx(const char *fmt, ...);  
  7. void event_msgx(const char *fmt, ...);  
  8. void _event_debugx(const char *fmt, ...);  

        這些函數都是聲明在log-internal.h檔案中。是以使用者并不能使用之,這些函數都是Libevent内部使用的。

        這些函數内部實作都差不多。下面是其中幾個實作

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. void  
  2. event_warn(const char *fmt, ...)  
  3. {  
  4.     va_list ap;  
  5.     va_start(ap, fmt);  
  6.     _warn_helper(_EVENT_LOG_WARN, strerror(errno), fmt, ap);  
  7.     va_end(ap);  
  8. }  
  9. void  
  10. event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...)  
  11. {  
  12.     va_list ap;  
  13.     int err = evutil_socket_geterror(sock);  
  14.     va_start(ap, fmt);  
  15.     _warn_helper(_EVENT_LOG_ERR, evutil_socket_error_to_string(err), fmt, ap);  
  16.     va_end(ap);  
  17.     event_exit(eval);  
  18. }  

        可以看到,主要是設定調用的日記級别,把可變參數用va_list變量記錄,然後調用_warn_helper這個輔助函數。其中,event_warn和event_warnx的差別是_warn_helper函數的第二個參數分别是strerror(errno)和NULL。

        而_warn_helper這個輔助函數也是蠻簡單的。

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. static void  
  2. _warn_helper(int severity, const char *errstr, const char *fmt, va_list ap)  
  3. {  
  4.     char buf[1024];  
  5.     size_t len;  
  6.     //如果有可變參數,就把可變參數格式化到一個緩存區buf中。  
  7.     if (fmt != NULL)  
  8.         evutil_vsnprintf(buf, sizeof(buf), fmt, ap);  
  9.     else  
  10.         buf[0] = '\0';  
  11.     //如果有額外的資訊描述,把這些資訊追加到可變參數的後面。  
  12.     if (errstr) {  
  13.         len = strlen(buf);  
  14.        //-3是因為還有另外三個字元,冒号、空格和\0。  
  15.         if (len < sizeof(buf) - 3) {  
  16.             evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", errstr);  
  17.         }  
  18.     }  
  19.     //把緩存區的資料作為一條日志記錄,調用Libevent的日志函數。  
  20.     event_log(severity, buf);//這個函數也是最前面的說到的那個日志處理函數  
  21. }  

        這些日志函數的使用,如同printf函數那樣,支援%s, %d之類的格式化輸出。比如:

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. event_warnx("Far too many %s (%d)", "wombats", 99);  

        值得注意的是,也如同printf那樣,可變參數中的參數要和第一個參數中的格式要求相比對。在這些日志函數的聲明中,如果是GNU 的編譯器,将會檢查是否比對。其聲明如下:

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. #ifdef __GNUC__  
  2. #define EV_CHECK_FMT(a,b) __attribute__((format(printf, a, b)))  
  3. #define EV_NORETURN __attribute__((noreturn))  
  4. #else  
  5. #define EV_CHECK_FMT(a,b)  
  6. #define EV_NORETURN  
  7. #endif  
  8. #define _EVENT_ERR_ABORT ((int)0xdeaddead)  
  9. void event_err(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3) EV_NORETURN;  
  10. void event_warn(const char *fmt, ...) EV_CHECK_FMT(1,2);  
  11. void event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...) EV_CHECK_FMT(3,4) EV_NORETURN;  
  12. void event_sock_warn(evutil_socket_t sock, const char *fmt, ...) EV_CHECK_FMT(2,3);  
  13. void event_errx(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3) EV_NORETURN;  
  14. void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2);  
  15. void event_msgx(const char *fmt, ...) EV_CHECK_FMT(1,2);  
  16. void _event_debugx(const char *fmt, ...) EV_CHECK_FMT(1,2);  

錯誤處理:

        Libevent庫運作的時候有可能會緻命發生錯誤,此時,Libevent的預設行為是終止程式。同日志處理一樣,使用者也是可以用Libevent定制自己的錯誤處理函數。錯誤處理函數的格式和定制函數如下:

[cpp]  view plain  copy  

Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
Libevent源碼分析-----日志和錯誤處理日志處理:錯誤處理:
  1. typedef void (*event_fatal_cb)(int err);  
  2. void event_set_fatal_callback(event_fatal_cb cb);  

        定制函數的内部實作原理和前面的日志處理函數一樣,都是通過一個全局函數指針變量存儲使用者的錯誤處理函數。

        在預設情況下,Libevent處理這些緻命錯誤時會粗暴地殺死程式,但大多數情況下,Libevent在調用這個緻命處理函數前都會調用前面的日志記錄函數,其級别是_EVENT_LOG_ERR。此時,雖然程式突然死了,但還是可以在日志中找到一些資訊。使用者的錯誤處理函數也應該殺死程式。

        如果要定制自己的日志處理函數和錯誤處理函數,那麼應該在程式的一開始位置就進行定制。

繼續閱讀