Libevent 源碼閱讀筆記(三)浸须、日志系統(tǒng)的實現(xiàn)

Libevent 如何定義自己的日志輸出格式

Libevent 的日志系統(tǒng)中定義了四個日志的輸出等級,默認情況下會將日志信息輸出到終端檬某。不過 Libevent 允許用戶設(shè)置自定義的日志輸出回調(diào)函數(shù)以及異常日志輸出函數(shù)替梨,以提供日志信息的進一步解釋和輸出重定向钓试。回調(diào)函數(shù)的指針定義如下:

//event.h
//日志輸出等級
#define EVENT_LOG_DEBUG 0   
#define EVENT_LOG_MSG   1
#define EVENT_LOG_WARN  2
#define EVENT_LOG_ERR   3
//用戶自定義的日志輸出回調(diào)函數(shù)副瀑,其中 severity 代表日志輸出等級
typedef void (*event_log_cb)(int severity, const char *msg);
typedef void (*event_fatal_cb)(int err);

Libevent 默認將這兩個回調(diào)函數(shù)置為 NULL弓熏,用戶可以通過函數(shù)event_set_log_callbackevent_set_fatal_callback進行設(shè)置

//log.c
static event_log_cb log_fn = NULL;
void event_set_log_callback(event_log_cb cb)
{
    log_fn = cb;
}
static event_fatal_cb fatal_fn = NULL;
void event_set_fatal_callback(event_fatal_cb cb)
{
    fatal_fn = cb;
}

若用戶沒有自定義日志輸出函數(shù),那么 Libevent 會根據(jù) severity 的值調(diào)用相應(yīng)的默認日志輸出函數(shù)糠睡,并向 stderr 輸出日志信息挽鞠。具體的實現(xiàn)如下:

//log.c
static void event_log(int severity, const char *msg)
{
    if (log_fn)
        log_fn(severity, msg);
    else {
        const char *severity_str;
        switch (severity) {
        case EVENT_LOG_DEBUG:
            severity_str = "debug";
            break;
        case EVENT_LOG_MSG:
            severity_str = "msg";
            break;
        case EVENT_LOG_WARN:
            severity_str = "warn";
            break;
        case EVENT_LOG_ERR:
            severity_str = "err";
            break;
        default:
            severity_str = "???";
            break;
        }
        //(void)的作用是減少編譯器因為“未被使用的返回值”而產(chǎn)生的警告信息。
        (void)fprintf(stderr, "[%s] %s\n", severity_str, msg);
    }
}

static void event_exit(int errcode)
{
    if (fatal_fn) {
        fatal_fn(errcode);
        exit(errcode); /* should never be reached */
    } else if (errcode == EVENT_ERR_ABORT_)
        abort();
    else
        exit(errcode);
}

日志系統(tǒng)的底層函數(shù)

看完了 Libevent 的日志系統(tǒng)留給用戶的兩個接口定義后狈孔,我們可以來看看 Libevent 默認的日志輸出函數(shù)信认。在源碼當(dāng)中,日志系統(tǒng)相關(guān)的文件主要有兩個: log.c 和 log-internal.h均抽。我們先來看 log-internal.h 文件

// log-internal.h 
#ifdef __GNUC__
#define EV_CHECK_FMT(a,b) __attribute__((format(printf, a, b)))
#define EV_NORETURN __attribute__((noreturn))
#else
#define EV_CHECK_FMT(a,b)
#define EV_NORETURN
#endif

//EVENT_DEBUG_LOGGING_ENABLED 為調(diào)試模式的開關(guān)
#ifdef EVENT_DEBUG_LOGGING_ENABLED
EVENT2_CORE_EXPORT_SYMBOL extern ev_uint32_t event_debug_logging_mask_;
#define event_debug_get_logging_mask_() (event_debug_logging_mask_)
#else
#define event_debug_get_logging_mask_() (0)
#endif

//EVENT_ERR_ABORT_ 代表終止程序時的錯誤碼
#define EVENT_ERR_ABORT_ ((int)0xdeaddead)

/* EVENT2_EXPORT_SYMBOL是定義在 include/event2/visibility.h 下的一個宏
 * define EVENT2_EXPORT_SYMBOL __attribute__ ((visibility("default")))
 */
EVENT2_EXPORT_SYMBOL
void event_err(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3) EV_NORETURN;
EVENT2_EXPORT_SYMBOL
void event_warn(const char *fmt, ...) EV_CHECK_FMT(1,2);
EVENT2_EXPORT_SYMBOL
void event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...) EV_CHECK_FMT(3,4) EV_NORETURN;
EVENT2_EXPORT_SYMBOL
void event_sock_warn(evutil_socket_t sock, const char *fmt, ...) EV_CHECK_FMT(2,3);
EVENT2_EXPORT_SYMBOL
void event_errx(int eval, const char *fmt, ...) EV_CHECK_FMT(2,3) EV_NORETURN;
EVENT2_EXPORT_SYMBOL
void event_warnx(const char *fmt, ...) EV_CHECK_FMT(1,2);
EVENT2_EXPORT_SYMBOL
void event_msgx(const char *fmt, ...) EV_CHECK_FMT(1,2);
EVENT2_EXPORT_SYMBOL
void event_debugx_(const char *fmt, ...) EV_CHECK_FMT(1,2);

EVENT2_EXPORT_SYMBOL
void event_logv_(int severity, const char *errstr, const char *fmt, va_list ap)
    EV_CHECK_FMT(3,0);

在 log-internal.h 中嫁赏,我們可以看到 Libevent 中的日志系統(tǒng)其實運用到了編譯器屬性 __attribute__ 的一些技巧。

__attribute__ 是 GNU C 下的一種機制油挥,可以用來設(shè)置函數(shù)屬性潦蝇、變量屬性以及類型屬性款熬,它的語法格式如下

__attribute__((attribute-list))

其中 attribute-list 是用逗號分隔開的列表。在 Libevent 的日志系統(tǒng)中攘乒,主要應(yīng)用到的有以下三種 __attribute__ 屬性:

1. __attribute__((format(archetype, string-index, first-to-check)))

該屬性可以為函數(shù)添加上類似于 print贤牛、scanf、strftime 以及 strfmon 風(fēng)格的格式化字符串则酝,從而可以幫助我們完成對格式化字符串的類型檢查殉簸。其中,archetype 代表參數(shù)的格式化風(fēng)格堤魁,string-index 代表格式化字符串位于形參列表中的哪個位置瓶佳,first-to-check 代表可變參數(shù)列表在形參列表中的哪個位置刷喜。結(jié)合一個 event_warn 的例子:

void event_warn(const char *fmt, ...) __attribute__((format(printf, 1, 2)));

__attribute__((format(printf, 1, 2))) 代表按照 printf 的風(fēng)格檢查格式化字符串,其中參數(shù)列表的第 1 個參數(shù)代表格式化字符串隐绵,第 2 個參數(shù)代表可變參數(shù)列表洞坑。

2. __attribute__((noreturn))

noreturn 是一個函數(shù)屬性盲链,用來通知編譯器該函數(shù)在執(zhí)行后不會將控制權(quán)返回給它的調(diào)用者。以函數(shù) event_err為例迟杂,當(dāng)程序使用該日志函數(shù)時刽沾,說明程序已然產(chǎn)生了嚴重的錯誤,不能再繼續(xù)執(zhí)行下去排拷,除了拋出異常侧漓,或者調(diào)用如exit()abort()函數(shù)終止程序以外,可以利用 noreturn 來處理监氢。當(dāng)函數(shù)執(zhí)行 event_err 時布蔗,主程序會將控制權(quán)交給 event_err 函數(shù),當(dāng) event_err 執(zhí)行完畢后浪腐,控制流將不會返回主程序纵揍,而是停留在此處。

3. __attribute__ ((visibility("default")))

visibility屬性可以將從動態(tài)庫中導(dǎo)出可見符號议街,default 代表可見泽谨,而 hidden 代表隱藏。新版本的 Linux 內(nèi)核限制了動態(tài)庫的符號可見性特漩,因為這樣做可以提高程序的模塊性吧雹,同時動態(tài)庫裝載和識別的符號越少,程序啟動和運行的速度也就越快涂身。Libevent 中被 EVENT2_EXPORT_SYMBOL 修飾的函數(shù)符號都將會被導(dǎo)出吮炕,因此使用該動態(tài)庫程序都可以使用到這些函數(shù)。

對于event_err访得,event_sock_err龙亲,event_debugx_等函數(shù)陕凹,它們的實現(xiàn)大同小異,區(qū)別僅是部分參數(shù)的不同鳄炉,以下選取部分函數(shù)進行討論

void event_err(int eval, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    event_logv_(EVENT_LOG_ERR, strerror(errno), fmt, ap);
    va_end(ap);
    event_exit(eval);
}

void event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...)
{
    va_list ap;
    int err = evutil_socket_geterror(sock);

    va_start(ap, fmt);
    event_logv_(EVENT_LOG_ERR, evutil_socket_error_to_string(err), fmt, ap);
    va_end(ap);
    event_exit(eval);
}

void event_debugx_(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    event_logv_(EVENT_LOG_DEBUG, NULL, fmt, ap);
    va_end(ap);
}

void event_logv_(int severity, const char *errstr, const char *fmt, va_list ap)
{
    char buf[1024];
    size_t len;
    //當(dāng) event_debug_get_logging_mask_() == 0 時關(guān)閉調(diào)試日志的輸出
    if (severity == EVENT_LOG_DEBUG && !event_debug_get_logging_mask_())
        return;

    if (fmt != NULL)
        //將日志信息按照格式字符串進行格式化杜耙,并存入 buf 中
        evutil_vsnprintf(buf, sizeof(buf), fmt, ap);
    else
        buf[0] = '\0';
    //若有額外的錯誤信息,則將其一起放入 buf 
    if (errstr) {
        len = strlen(buf);
        if (len < sizeof(buf) - 3) {
            evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", errstr);
        }
    }

    event_log(severity, buf);
}

上述的日志函數(shù)大體的結(jié)構(gòu)是相同的拂盯,都采用了可變參數(shù)列表的形式實現(xiàn)佑女,最終通過event_logv_以及event_log兩個函數(shù)將日志輸出。

一些語法糖

在 log-internal.h 當(dāng)中還定義了一個 event_debug 的宏谈竿,可以用于在調(diào)試期間打印出調(diào)試信息团驱。利用這個宏來代替event_debugx_函數(shù),一方面可以使得調(diào)試語義更加清晰空凸,另一方面也可以非常方便地實現(xiàn)調(diào)試模式的開關(guān)嚎花。它的定義如下:

#ifdef EVENT_DEBUG_LOGGING_ENABLED
#define event_debug(x) do {         \
    if (event_debug_get_logging_mask_()) {  \
        event_debugx_ x;        \
    }                   \
    } while (0)
#else
#define event_debug(x) ((void)0)
#endif

總結(jié)

  1. Libevent 的日志系統(tǒng)為用戶提供了兩個接口event_log_cbevent_fatal_cb,以便用戶實現(xiàn)自定義的日志處理功能呀洲,在需要的時候可以使用event_set_log_callbackevent_set_fatal_callback進行設(shè)置
  2. 如果用戶沒有定義自己的日志處理回調(diào)函數(shù)紊选,則采用 Libevent 默認的日志處理系統(tǒng),它將日志輸出等級定義為四個道逗,并會將日志輸出到終端兵罢。
  3. 在日志系統(tǒng)的開發(fā)過程當(dāng)中,利用好 GNU C 的一些機制滓窍,如 __attribute__ 等卖词,可以極大簡化一些繁瑣的檢查過程,不僅有利于調(diào)試吏夯,幫助找到一些難以發(fā)現(xiàn)的錯誤此蜈,還有助于編譯器進行優(yōu)化。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锦亦,一起剝皮案震驚了整個濱河市舶替,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌杠园,老刑警劉巖顾瞪,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異抛蚁,居然都是意外死亡陈醒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門瞧甩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钉跷,“玉大人,你說我怎么就攤上這事肚逸∫蓿” “怎么了彬坏?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長膝晾。 經(jīng)常有香客問我栓始,道長,這世上最難降的妖魔是什么血当? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任幻赚,我火速辦了婚禮,結(jié)果婚禮上臊旭,老公的妹妹穿的比我還像新娘落恼。我一直安慰自己,他們只是感情好离熏,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布佳谦。 她就那樣靜靜地躺著,像睡著了一般撤奸。 火紅的嫁衣襯著肌膚如雪吠昭。 梳的紋絲不亂的頭發(fā)上喊括,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天胧瓜,我揣著相機與錄音,去河邊找鬼郑什。 笑死府喳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蘑拯。 我是一名探鬼主播钝满,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼申窘!你這毒婦竟也來了弯蚜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤剃法,失蹤者是張志新(化名)和其女友劉穎碎捺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贷洲,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡收厨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了优构。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诵叁。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钦椭,靈堂內(nèi)的尸體忽然破棺而出拧额,到底是詐尸還是另有隱情碑诉,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布侥锦,位于F島的核電站联贩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捎拯。R本人自食惡果不足惜泪幌,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望署照。 院中可真熱鬧祸泪,春花似錦、人聲如沸建芙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禁荸。三九已至右蒲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赶熟,已是汗流浹背瑰妄。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留映砖,地道東北人间坐。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像邑退,于是被迫代替她去往敵國和親竹宋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355