1. 程式人生 > >Libevent通過va_list實現日誌功能

Libevent通過va_list實現日誌功能

Libevent提供一個記錄錯誤和警告資訊的日誌功能。預設是直接將上述資訊輸出到標準錯誤,同時也可以通過回撥函式提供自己的日誌函式覆蓋預設的功能。

1、C語言實現可變引數

<stdarg.h>中包含一組巨集定義,它們對如何遍歷引數進行了定義。依據下面的幾個函式,可以寫出高效率的日誌功能。


va_list//巨集定義,用於宣告變數,該變數將依次引用各引數。

void va_start (va_list ap, paramN);//ap前面定義的引數指標,param是函式列表最後一個有名引數。

type va_arg (va_list ap, type);//該函式返回一個引數,並將ap指向下一個引數,使用type型別名決定返回物件的型別、指標移動步長。
va_end//確保清理了ap對應的記憶體 int vsnprintf (char * s, size_t n, const char * format, va_list arg ); /* 按照指定格式將可變引數寫入字串快取區。 s: Pointer to a buffer where the resulting C-string is stored.The buffer should have a size of at least n characters. n: Maximum number of bytes to be used in the buffer.The generated string has a length of at most n-1, leaving space for the additional terminating null character.函式自動在轉換完成的字串後面加上終止字元\0。size_t is an unsigned integral type. format: C string that contains a format string that follows the same specifications as format in printf (see printf for details). arg: A value identifying a variable arguments list initialized with va_start. */

簡單demo:

#include <stdio.h>
#include <stdarg.h>

void PrintFError ( const char * format, ... )
{
  char buffer[256];//快取
  va_list args;//ap變數
  va_start (args, format);//初始化ap
  vsnprintf (buffer , 256 , format , args);//按照format將可變引數列表轉換成對應字串
  printf("%s" , buffer);//輸出字串
  va_end (args);//清理ap
} int main () { FILE * pFile; char szFileName[]="myfile.txt"; pFile = fopen (szFileName,"r"); if (pFile == NULL) PrintFError ("Error opening %s %d \n",szFileName , 555); else { fclose (pFile); } return 0; }

這裡寫圖片描述

2、libevent實現日誌功能

void event_err(int eval, const char *fmt, ...);
void event_warn(const char *fmt, ...);
void event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...) ;
void event_sock_warn(evutil_socket_t sock, const char *fmt, ...);
void event_errx(int eval, const char *fmt, ...);
void event_warnx(const char *fmt, ...);
void event_msgx(const char *fmt, ...);
void _event_debugx(const char *fmt, ...);

上述這些函式都定義在<log-internal.h>,說明這些函式只能被libevent內部呼叫,用於將一些不合理的情況打印出來,告知使用者。
這些函式的實現非常簡單。例如event_err。其具體實現如下。

/*
eval:表示錯誤碼
fmt:固定格式
*/
void
event_err(int eval, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    _warn_helper(_EVENT_LOG_ERR, strerror(errno), fmt, ap);
    va_end(ap);
    event_exit(eval);
}


/*
供event_err等上述函式內部呼叫的函式。
severity:日誌類別
    EVENT_LOG_DEBUG        除錯日誌
    #define EVENT_LOG_MSG  訊息日誌
    #define EVENT_LOG_WARN 警告日誌
    #define EVENT_LOG_ERR  錯誤日誌
errstr:額外的錯誤資訊,也就是一行字串,用於提示操作人員
fmt:固定格式
ap:可變引數變數   
*/
static void
_warn_helper(int severity, const char *errstr, const char *fmt, va_list ap)
{
    char buf[1024];
    size_t len;
    //如果有可變引數,把引數格式化到緩衝區
    if (fmt != NULL)
        evutil_vsnprintf(buf, sizeof(buf), fmt, ap);//呼叫vsnprintf將可變引數寫入buf
    else
        buf[0] = '\0';

    //如果有額外資訊描述,資訊追加到可變引數的後面
    if (errstr) {
        len = strlen(buf);
        if (len < sizeof(buf) - 3) {//-3 為了多存三個字元 冒號 空格 和\0
            evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", errstr);//儲存
        }
    }

    event_log(severity, buf);//緩衝區作為一條日誌,輸出
}

/*
log_fn是函式指標,可以由使用者配置的回撥函式,當此函式賦值時候,對應的錯誤處理將執行此回撥函式。
*/
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)fprintf(stderr, "[%s] %s\n", severity_str, msg);//輸出到標準錯誤終端
    }
}

通過上述分析函式呼叫過程依次是:event_err-> _warn_helper->event_log將錯誤資訊輸出到終端顯示,如果使用者設定了對應的錯誤回撥函式,那麼將執行回撥函式,用於可以在回撥函式裡面儲存對應的錯誤資訊,生成以後的日誌檔案。

將函式分離出來測試:

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <event.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <errno.h>
int
evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap);
int
evutil_snprintf(char *buf, size_t buflen, const char *format, ...)
{
    int r;
    va_list ap;
    va_start(ap, format);
    r = evutil_vsnprintf(buf, buflen, format, ap);
    va_end(ap);
    return r;
}
int
evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap)
{
    int r;
    if (!buflen)
        return 0;
    r = vsnprintf(buf, buflen, format, ap);//轉換,最多儲存buflen-1個字元,最後儲存\0
    return r;
}
static void
event_log(int severity, const char *msg)
{
        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)fprintf(stderr, "[%s] %s\n", severity_str, msg);//輸出到標準錯誤終端
}


static void
_warn_helper(int severity, const char *errstr, const char *fmt, va_list ap)
{
    char buf[1024];
    size_t len;
    //如果有可變引數,把引數格式化到緩衝區
    if (fmt != NULL)
        evutil_vsnprintf(buf, sizeof(buf), fmt, ap);//將變引數寫入buf
    else
        buf[0] = '\0';

    //如果有額外資訊描述,資訊追加到可變引數的後面
    if (errstr) {
        len = strlen(buf);//已經儲存字元長度
        if (len < sizeof(buf) - 3) {//-3 為了多存三個字元 冒號 空格 和\0,否則出現問題
            evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", errstr);
        }
    }
    event_log(severity, buf);//緩衝區作為一條日誌,輸出
}

void
event_err(int eval, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    _warn_helper(_EVENT_LOG_ERR, "this is extra info", fmt, ap);
    va_end(ap);
    exit(eval);
}

void event_log_callback(int severity, const char *msg)
{
    printf("%s" , msg);
}

int main(void)
{
    event_err(0, "%s: failed to register rpc %s %d",
        __func__ , "wangjun" ,12);
    return 0;
}

這裡寫圖片描述
在此,libevent的日誌功能分析完畢。這是一個非常簡陋的日誌功能。

3、自定義回撥日誌函式例子

#include <event2/event.h>
#include <stdio.h>

static void discard_cb(int severity, const char *msg)
{
    /* This callback does nothing. */
}

static FILE *logfile = NULL;
static void write_to_file_cb(int severity, const char *msg)
{
    const char *s;
    if (!logfile)
        return;
    switch (severity) {
        case _EVENT_LOG_DEBUG: s = "debug"; break;
        case _EVENT_LOG_MSG:   s = "msg";   break;
        case _EVENT_LOG_WARN:  s = "warn";  break;
        case _EVENT_LOG_ERR:   s = "error"; break;
        default:               s = "?";     break; /* never reached */
    }
    fprintf(logfile, "[%s] %s\n", s, msg);
}

/* Turn off all logging from Libevent. */
void suppress_logging(void)//設定沒有日誌功能
{
    event_set_log_callback(discard_cb);
}

/* Redirect all Libevent log messages to the C stdio file 'f'. */
void set_logfile(FILE *f)//設定將日誌寫入檔案,f為對應的檔案描述符符
{
    logfile = f;
    event_set_log_callback(write_to_file_cb);
}

注意:在回撥函式裡面不要呼叫libevent內部的函式,否則會出現難以發現的bug。