文章內容和代碼爲作者原創,轉載請說明 ^_^
這篇文章主要介紹一下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);
}