LevelDB源碼分析之十:LOG文件
首先要區(qū)分LOG文件和.log文件。
LOG文件:用來記錄數(shù)據(jù)庫打印的運(yùn)行日志信息,方便bug的查找。
.log文件:在LevelDB中的主要作用是系統(tǒng)故障恢復(fù)時(shí),能夠保證不會(huì)丟失數(shù)據(jù)。因?yàn)樵趯⒂涗泴懭雰?nèi)存的Memtable之前,會(huì)先寫入.log文件,這樣即使系統(tǒng)發(fā)生故障,Memtable中的數(shù)據(jù)沒有來得及Dump到磁盤的SSTable文件,LevelDB也可以根據(jù).log文件恢復(fù)內(nèi)存的Memtable數(shù)據(jù)結(jié)構(gòu)內(nèi)容,不會(huì)造成系統(tǒng)丟失數(shù)據(jù)。
Env.h中定義了操作LOG文件的虛基類Logger,只提供了一個(gè)對(duì)外的接口Logv。Logger的Windows版本實(shí)現(xiàn)是WinLogger。
//?win_logger.h
class?WinLogger?:?public?Logger?{
?private:
??FILE*?file_;
?public:
??explicit?WinLogger(FILE*?f)?:?file_(f)?{?assert(file_);?}
??virtual?~WinLogger()?{
????fclose(file_);
??}
??virtual?void?Logv(const?char*?format,?va_list?ap);
};//?win_logger.cc
void?WinLogger::Logv(const?char*?format,?va_list?ap)?{
??//?獲取當(dāng)前線程ID
??const?uint64_t?thread_id?=?static_cast(::GetCurrentThreadId());
??//?We?try?twice:?the?first?time?with?a?fixed-size?stack?allocated?buffer,
??//?and?the?second?time?with?a?much?larger?dynamically?allocated?buffer.
??//?嘗試兩次內(nèi)存分配:第一次分配固定大小的棧內(nèi)存,如果不夠,第二次分配更大的堆內(nèi)存
??char?buffer[500];
??for?(int?iter?=?0;?iter?<?2;?iter++)?{
????char*?base;
????int?bufsize;
????if?(iter?==?0)?{
??????bufsize?=?sizeof(buffer);
??????base?=?buffer;
????}?else?{
??????bufsize?=?30000;
??????base?=?new?char[bufsize];
????}
????char*?p?=?base;
????char*?limit?=?base?+?bufsize;
????SYSTEMTIME?st;
????//?GetSystemTime?returns?UTC?time,?we?want?local?time!
????::GetLocalTime(&st);
????p?+=?_snprintf_s(p,?limit?-?p,?_TRUNCATE,
??????"%04d/%02d/%02d-%02d:%02d:%02d.%03d?%llx?",
??????st.wYear,
??????st.wMonth,
??????st.wDay,
??????st.wHour,
??????st.wMinute,
??????st.wSecond,
??????st.wMilliseconds,
??????static_cast(thread_id));
????//?Print?the?message
????if?(p?<?limit)?{
??????va_list?backup_ap?=?ap;
???//?limit-p是p可接受的最大字符數(shù)
??????p?+=?vsnprintf(p,?limit?-?p,?format,?backup_ap);
??????va_end(backup_ap);
????}
????//?Truncate?to?available?space?if?necessary
????//?如果第一次分的棧內(nèi)存不夠,會(huì)第二次分配更大的堆內(nèi)存。
????//?如果第二次分的內(nèi)存還不夠,只能對(duì)存放內(nèi)容做截?cái)嗵幚砹恕?????//?為什么p==limit也算內(nèi)存不夠呢?因?yàn)樽詈笠娣艙Q行符,
????//?所以有效的內(nèi)容最大長度只能是limit-p-1。
????if?(p?>=?limit)?{
??????if?(iter?==?0)?{
????????continue;?//?Try?again?with?larger?buffer
??????}?else?{
????????p?=?limit?-?1;
??????}
????}
????//?Add?newline?if?necessary
????//?如果p==base或者有效內(nèi)容的最后一個(gè)字符不是換行符
????//?則將換行符添加到最后
????if?(p?==?base?||?p[-1]?!=?'n')?{
??????*p++?=?'n';
????}
????assert(p?<=?limit);
????fwrite(base,?1,?p?-?base,?file_);
????//?fwrite只是將寫入內(nèi)容放入緩存中,真正輸出到文件是通過fflush實(shí)現(xiàn)的。
????fflush(file_);
????//?如果分配了堆內(nèi)存,需要手動(dòng)釋放。
????if?(base?!=?buffer)?{
??????delete[]?base;
????}
????break;
??}
}我用的是Windows版LevelDB,上面這段代碼在Windows 7上用VS2013調(diào)試時(shí)是有bug的。當(dāng)要打印的日志信息長度超過30000時(shí),vsnsprintf會(huì)截?cái)嘈畔ⅲ莢snsprintf并不會(huì)返回被截?cái)嗲暗男畔⒌拈L度,而是返回-1,這樣一來,那一行日志信息只會(huì)打印出日期、時(shí)間和線程號(hào),盡管一行日志超過30000字節(jié)的概率非常小。經(jīng)查證,vsnsprintf的返回值和操作系統(tǒng)、編譯器有關(guān)。
經(jīng)測(cè)試,Windows10上用VS2015時(shí)vsnsprintf能返回期望結(jié)果——被截?cái)嗲暗男畔⒌拈L度。
個(gè)人覺得這段代碼非常值得學(xué)習(xí)。首先是考慮了內(nèi)存分配的情況。通??吹降娜罩緦?shí)現(xiàn)中,要么是在棧中設(shè)定一個(gè)定長緩沖區(qū),然后設(shè)定一個(gè)日志最大長度,以此來避免內(nèi)存分配;要么是直接分配一大塊內(nèi)存來放日志字符串。這里作者使用了兩級(jí)“內(nèi)存分配”,首先在棧中分配500個(gè)字節(jié)的緩沖區(qū),這樣長度小于500字節(jié)的日志就可以避免掉new的開銷。如果發(fā)現(xiàn)放不下,再去new一個(gè)大塊內(nèi)存。當(dāng)然如果單行日志太大,超過了30000字節(jié),那么就直接做截?cái)嗔?。其次就是?nèi)存的防越界處理。
不過這里有個(gè)疑問,為何添加換行符的時(shí)候要判斷p==base,知道的同學(xué)請(qǐng)指點(diǎn)下。
最后在Env.h中封裝了一個(gè)全局的方法Log,方便調(diào)用接口Logv,如下所示。
void?Log(Logger*?info_log,?const?char*?format,?...)?{
??if?(info_log?!=?NULL)?{
????va_list?ap;
????va_start(ap,?format);
????info_log->Logv(format,?ap);
????va_end(ap);
??}
}調(diào)用方法:Log(result.info_log, "Ignoring error %s","燦哥哥的博客");





