UtilBox(ub)基礎組件 -- Log日誌(1)

      文章內容和代碼爲作者原創,轉載請說明 ^_^

      這篇文章主要介紹一下log組件,平時大家調試程序和記錄程序異常,這是最常用的。比如調試小程序的正確性,有些同學就在代碼裏放一堆的printf,可是這樣帶來的後果就是想去掉這寫debug用的printf很麻煩(這個可以用宏來代替,比如類似ASSERT,通過開關來控制)。還有,如果程序寫成了daemon,放到後臺,printf的作用就沒了。所以,日誌纔是萬能的,即方便追查程序也方便日後幫你統計數據,並且每行日誌也可以加入文件行號、時間等有用信息。

      log雖然能帶來不少好處,但也會有不好的地方,比如程序每次寫日誌都會帶來性能損耗(寫入pagecache會好一點),我的程序統計過,一個proxy程序不斷打日誌的話,會帶來10%~20%的性能損耗,所以日誌不是隨便打,沒用的信息就不要打出來,而且日誌要分級(DEBUG/TRACE/WARNING/FATAL四級),方便以後把不需要的級別的日誌過濾掉,比如開發時候DEBUG級別的日誌需要打出來,上了線就不要打DEBUG了,只打印WARNING的一些重要信息就好了。

      作爲日誌,還有一點比較叫重要的功能就是要做日誌的切分,比如日誌文件大於2G時就需要重新new一個文件,還有比如我們需要一個小時切分一個日誌文件,方便以後查詢。OK,下面就看看log組件需要的接口函數:

      對日誌進行初始化,如果filepath是NULL的話就用stdout,相當於printf了,第二個參數是是否要做DIRECT_IO的方式,說明一下directio是write時直接“寫穿”到磁盤,而不像一般情況下,會寫入到linux的pagecache(pdflush會定時batch flush到磁盤上),所以非directio在只有機器掉電的情況下會丟失一些日誌的,directio不會,它會保證內容寫到磁盤才返回。一般情況下,只要這寫日誌不作爲redo_log或者binlog這種需要重放磁盤數據的,就不要用directio,因爲性能會收到很大影響。

/**
 * @brief  : initial log handler with path , indicate use direct io or not
 * 
 * @params : [in] filepath  : which file to be written . if NULL "stdout" will be set
 * @params : [in] flag      : file mask set         
 *
 * @return : pointer to the log_t structure . if failed , it's NULL
 */
log_t* log4c_init(const char* filepath, int direct_io);

      帶切分的初始化(上面的init不切分),可以按時間ts,也可以按內容ss,或者兩者都有,如果某項不需要的話就爲0

/**
 * @brief  : initial log handler with path , direct io , and split strategy 
 * 
 * @params : [in] filepath  : which file to be written . if NULL "stdout" will be set
 * @params : [in] flag      : file mask set         
 * @params : [in] ts        : span time of split file , 0 means no split 
 * @params : [in] ss        : content size of split file , 0 means no split 
 *
 * @return : pointer to the log_t structure . if failed , it's NULL
 */
log_t* log4c_split_init(const char* filepath, int direct_io,unsigned long long ts, size_t ss);


      銷燬日誌句柄,沒什麼可說的了。最初設計成log4c_destroy(log*)這種,可是每次log4c_write時候需要用戶穿一個句柄進來就的顯得累贅,而且一般用戶也不會有兩個日誌句柄,所以就放到內部了。

_/**
 * @brief  : destroy log handle 
 */
void log4c_destroy();


        針對不同級別的log進行寫入,說明一下,DEBUG和TRACE級別會寫入.log文件,而WARNING和FATAL日誌會寫入.log.wf文件,查起來方便。其實就是個宏來調用write,這裏用宏的原因就是沒行日誌有當前的文件名和行號,所以用宏。

/**
 * @brief :  write log with LEVEL 
 *
 * @params : [in] log : log handle 
 * @params : [in] buf : buffer to be written 
 */
#define LOG4C_DEBUG(buf) do{\
    log4c_write(DEBUG,__FILE__,__LINE__,buf);\
}while(0)

#define LOG4C_TRACE(buf) do{\
    log4c_write(TRACE,__FILE__,__LINE__,buf);\
}while(0)

#define LOG4C_WARNNING(buf) do{\
    log4c_write(WARNNING,__FILE__,__LINE__,buf);\
}while(0)

#define LOG4C_FATAL(buf) do{\
    log4c_write(FATAL,__FILE__,__LINE__,buf);\
}while(0)


      最後,來說一下log_t這個最核心的數據結構,log的句柄,裏面記錄了2個文件fd,以及時間的記錄,和切分的時間、容量的閥值和文件掩碼等信息

typedef struct __log_t
{
    //unsigned int level;   /* level of log to record : ERROR , WARNING , DBUG , ALL */
    char file_name[FILE_NAME_MAX];
    unsigned int mask;
    int direct_io;
    int normal_fd;              /* output log file */
    int error_fd;               /* output wf file */
    size_t normal_file_size;        /* file size record */
    size_t error_file_size;         /* file size record */
    size_t split_size;              /* one piece(file) max size with split */
    unsigned long long time_used;   /* total time */
    unsigned long long split_time;  /* one piece(file) time with split */
    const char* last_error;
} log_t;

       處於文章比較長哈,之後我會把全部.c文件的實現拿出來說明。大家也可以根據上面的框架自己構造一個適合自己的log,畢竟合適的纔是最好的。

       先把重要的log4c_write貼出來 :(比較重要的是__check_file_stat這個函數。用來檢測時間和容量是否到了閥值如果到了,就close原來的fd,並且重新打開一個以當前時間結尾的文件,繼續寫入)

void log4c_write(log_level level, const char* file, unsigned int line, const char* buf)
{
    if (!my_log || !buf)
        return;

    // stdout ignore
    if ((my_log->normal_fd!=1) && (my_log->split_size!=0 || my_log->split_time!=0))
        __check_file_stat(my_log);

    int fd = -1;
    switch(level) {
        case DEBUG:
        case TRACE:
            fd = my_log->normal_fd;
            break;
        case WARNNING:
        case FATAL:
            fd = my_log->error_fd;
            break;
        default :
            fd = my_log->error_fd;
            break;
    }

    char tmp[1024] = {0};
    char *level_string = NULL;
    if (my_log->direct_io) {
        if (-1 == posix_memalign((void**)&level_string,getpagesize(),1024))
            return;
    } else
        level_string = tmp;


    int level_string_len = 0;
    // get ms
    time_t timep;
    struct tm *p;
    time(&timep);
        p=gmtime(&timep);

    switch(level) {
        case DEBUG:
            level_string_len = snprintf(level_string,FILE_NAME_MAX,"[DEBUG] [%s:%d] [%d/%d/%d:%d:%d] ",file,line,(1+p->tm_mon),p->tm_mday,p->tm_hour,p->tm_min,
            break;
        case TRACE:
            level_string_len = snprintf(level_string,FILE_NAME_MAX,"[TRACE] [%s:%d] [%d/%d/%d:%d:%d] ",file,line,(1+p->tm_mon),p->tm_mday,p->tm_hour,p->tm_min,
            break;
        case WARNNING:
            level_string_len = snprintf(level_string,FILE_NAME_MAX,"[WARNNING] [%s:%d] [%d/%d/%d:%d:%d] ",file,line,(1+p->tm_mon),p->tm_mday,p->tm_hour,p->tm_m
            break;
        case FATAL:
            level_string_len = snprintf(level_string,FILE_NAME_MAX,"[FATAL] [%s:%d] [%d/%d/%d:%d:%d] ",file,line,(1+p->tm_mon),p->tm_mday,p->tm_hour,p->tm_min,
            break;
        default :
            level_string_len = snprintf(level_string,FILE_NAME_MAX,"[UNKNOWN] [%s:%d] [%d/%d(%d:%d:%d)] ",file,line,(1+p->tm_mon),p->tm_mday,p->tm_hour,p->tm_m
            break;
    }
    int written = write(fd,level_string,level_string_len);
    size_t buf_len = strnlen(buf,512);
    written += write(fd,buf,buf_len);
    // append end "\n"
    written += write(fd,"\n",1);
    if (-1 != written) {
        size_t *file_size = (fd==my_log->error_fd) ? &my_log->error_file_size: &my_log->normal_file_size;
        *file_size += written;
        my_log->time_used += (unsigned long long)time(NULL);
    }

    if (my_log->direct_io)
        free(level_string);
}


發佈了33 篇原創文章 · 獲贊 64 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章