本項(xiàng)目涉及的到所有源碼見以下鏈接:
https://gitee.com/ace-zhe/wz_log
一、項(xiàng)目簡(jiǎn)介
1.日志的概念(白話版)
? ? ? ?日志類似于日記,通常是指對(duì)完成某件事情的過(guò)程中狀態(tài)等的記錄,而計(jì)算機(jī)中的日志是指日志數(shù)據(jù),是有價(jià)值的信息寶庫(kù),各種操作系統(tǒng)、應(yīng)用程序、設(shè)備和安全產(chǎn)品的日志數(shù)據(jù)能夠幫助你提前發(fā)現(xiàn)和避開災(zāi)難,找到安全事件的根本原因。
本項(xiàng)目涉及到的日志具體是指:程序運(yùn)行過(guò)程中所記錄的程序運(yùn)行狀態(tài)信息。
2.日志系統(tǒng)功能概述
? ? ? ?記錄程序運(yùn)行狀態(tài)信息,以便程序員能隨時(shí)根據(jù)狀態(tài)信息對(duì)系統(tǒng)運(yùn)行狀態(tài)進(jìn)行分析,能夠讓用戶在運(yùn)行測(cè)試程序時(shí)非常簡(jiǎn)便的進(jìn)行日志的輸出及控制。
3.日志系統(tǒng)具體功能
- 支持多級(jí)別日志消息
- 支持多線程并發(fā)寫入
- 支持同步日志和異步日志
- 支持寫入日志到控制臺(tái)、文件及滾動(dòng)文件中
- 支持?jǐn)U展不同的日志落地目標(biāo)地
4.日志系統(tǒng)的必要性
- 生產(chǎn)環(huán)境的產(chǎn)品為了保證其穩(wěn)定性及安全性不允許開發(fā)人員附加調(diào)試器去排查問(wèn)題,可以借助日志系統(tǒng)來(lái)打印一些日志幫助開發(fā)人員解決問(wèn)題。
- 上線客戶端的產(chǎn)品出現(xiàn)bug無(wú)法復(fù)現(xiàn)并解決時(shí),可以借助日志系統(tǒng)打印日志并上傳到服務(wù)器幫助開發(fā)人員進(jìn)行分析。
- 對(duì)于一些高頻操作(定時(shí)器、心跳包)在少量調(diào)試次數(shù)下可能無(wú)法觸發(fā)我們想要的行為,通過(guò)斷點(diǎn)的暫停方式,我們不得不重復(fù)操作幾十次、上百次甚至更多,導(dǎo)致排查問(wèn)題的效率低下,可以借助打印日志的方式排查。
- 在分布式、多線程、多進(jìn)程的代碼中,出現(xiàn)bug比較難以定位,可以借助日志系統(tǒng)打印日志幫助定位bug。
- 幫助首次接觸項(xiàng)目代碼的新開發(fā)人員理解代碼的運(yùn)行過(guò)程。
5.開發(fā)環(huán)境
- CentOS 7
- vscode/vim
- g++/gdb
- Makefile
6.核心技術(shù)
- 基于封裝、繼承、多態(tài)的面向?qū)ο蟮念悓哟卧O(shè)計(jì)
- 設(shè)計(jì)模式(單例、工廠、代理、建造者等)
- 生產(chǎn)者消費(fèi)者模型
- 多線程應(yīng)用
- 雙緩沖區(qū)(主要針對(duì)異步日志)
- C++11相關(guān)(多線程、auto、智能指針、右值引用等)
二、日志系統(tǒng)的技術(shù)實(shí)現(xiàn)
日志系統(tǒng)的技術(shù)實(shí)現(xiàn)主要包括三種類型:
- 利用printf/std::cout等輸出函數(shù)將日志信息打印到控制臺(tái)【實(shí)際項(xiàng)目中不用】
- 對(duì)于大型商業(yè)化項(xiàng)目,為方便排查問(wèn)題,我們一般會(huì)將日志輸出到文件或者是數(shù)據(jù)庫(kù)系統(tǒng)方便查詢分析,主要分為同步日志方式和異步日志方式。
1.同步寫日志
? ? ? ?同步寫日志是指輸出日志時(shí),必須等待日志輸出語(yǔ)句執(zhí)行完畢后才能執(zhí)行后面的業(yè)務(wù)邏輯語(yǔ)句,日志輸出語(yǔ)句與程序業(yè)務(wù)邏輯語(yǔ)句是在同一線程運(yùn)行,這種情況在高并發(fā)場(chǎng)景下,隨著日志數(shù)量的不斷增加,日志系統(tǒng)容易產(chǎn)生瓶頸:一方面,大量的打印陷入等量write系統(tǒng)調(diào)用,有一定開銷;另一方面,使得打印日志的進(jìn)程附帶了大量同步的磁盤IO,影響程序性能,其結(jié)構(gòu)圖如下:
2.異步寫日志
? ? ? ?基于同步寫日志的缺陷,出現(xiàn)了異步寫日志,異步寫日志是指在進(jìn)行 日志輸出是,日志輸出語(yǔ)句與業(yè)務(wù)邏輯語(yǔ)句并不在同一線程中運(yùn)行,而是有專門的線程用于日志輸出操作。業(yè)務(wù)線程只需要將日志放到一個(gè)內(nèi)存緩沖器中不用等待即可繼續(xù)執(zhí)行后續(xù)的業(yè)務(wù)邏輯,而日志的落地操作交給單獨(dú)的日志線程去完成,這也可以看做一個(gè)生產(chǎn)-消費(fèi)模型,這樣做的好處是即使日志沒(méi)有真的完成傳輸也不會(huì)影響程序的主業(yè)務(wù),可以提高程序的性能,其結(jié)構(gòu)圖如下:
三、項(xiàng)目前置知識(shí)補(bǔ)充
1.不同風(fēng)格不定參函數(shù)的用法
-
不定參宏函數(shù)
#include<stdio.h> #define Log(fmt,...) printf("[%s:%d]"fmt,__FILE__,__LINE__,##__VA_ARGS__) int main() { Log("hello wz!\n"); Log("%s-%d\n","hello world!",99); return 0; }
測(cè)試結(jié)果:
-
C風(fēng)格的不定參函數(shù)
#include<stdio.h> #include<stdarg.h> #include<stdlib.h> //按順序打印輸入的指定數(shù)量的整數(shù) void PrintNum(int count,...) { va_list ap; va_start(ap,count); int i=0; for(i=0;i<count;i++) { int num=va_arg(ap,int); printf("param:[%d]-%d\n",i,num); } va_end(ap); } //模擬實(shí)現(xiàn)printf void myprintf(const char* fmt,...) { va_list ap; va_start(ap,fmt); char* res; int ret=vasprintf(&res,fmt,ap); if(ret!=-1) { printf(res); free(res); } va_end(ap); } //測(cè)試代碼 int main() { PrintNum(5,1,2,3,4,5); PrintNum(3,666,999,555); myprintf("%s-%d:%s\n","wz",666,"nice"); return 0; }
測(cè)試結(jié)果:
-
C++風(fēng)格的不定參函數(shù)
//C++風(fēng)格的不定參函數(shù)的實(shí)現(xiàn)+測(cè)試 #include<iostream> void xprintf() { std::cout<<std::endl; } //利用C++風(fēng)格設(shè)計(jì)一個(gè)不定參的打印函數(shù) template<typename T,typename ...Args> void xprintf(const T& v,Args &&... args) { std::cout<<v; if((sizeof ...(args))>0) { xprintf(std::forward<Args>(args)...); } else { xprintf(); } } int main() { xprintf("wz",666,7.8); return 0; }
測(cè)試結(jié)果:
2.設(shè)計(jì)模式
概念:
?設(shè)計(jì)模式是前輩們對(duì)代碼開發(fā)經(jīng)驗(yàn)的總結(jié),是解決特定問(wèn)題的一系列套路,它不是語(yǔ)法規(guī)定,而是一套用來(lái)提高代碼可復(fù)用性、可維護(hù)性、可讀性、穩(wěn)健性以及安全性的解決方案。
設(shè)計(jì)代碼應(yīng)該遵循的六大原則:
單一職責(zé)原則告訴我們實(shí)現(xiàn)類要職責(zé)單一;
里氏替換原則告訴我們不要破壞繼承體系;
依賴倒置原則告訴我們要面向接口編程;
接口隔離原則告訴我們?cè)谠O(shè)計(jì)接口的時(shí)候要精簡(jiǎn)單一;
迪米特法則告訴我們要降低耦合;
開閉原則是總綱告訴我們要擴(kuò)展開放,關(guān)閉修改;
? ? ? ? 以下幾種設(shè)計(jì)者模式一定程度上應(yīng)用了這些原則,因此我們?cè)趯?shí)際工程項(xiàng)目中應(yīng)視情況選擇合適的設(shè)計(jì)模式來(lái)編寫代碼,下面就介紹幾種常見的。
單例模式
概念:
一個(gè)類只能創(chuàng)建一個(gè)對(duì)象,即單例模式,該設(shè)計(jì)模式可以保證系統(tǒng)中該類只有一個(gè)實(shí)例,并提供 一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn),該實(shí)例被所有程序模塊共享,比如在某個(gè)服務(wù)器程序中,該服務(wù)器的配置信息存放在一個(gè)文件中,這些配置數(shù)據(jù)由一個(gè)單例對(duì)象統(tǒng)一讀取,然后服務(wù)程序進(jìn)程中的其它對(duì)象再通過(guò)這個(gè)單例對(duì)象獲取這些配置信息,這種方式簡(jiǎn)化了復(fù)雜環(huán)境下額配置管理。
單例模式分兩種實(shí)現(xiàn)模式:餓漢模式和懶漢模式
餓漢模式:
程序啟動(dòng)時(shí)就會(huì)創(chuàng)建一個(gè)唯一的實(shí)例對(duì)象。應(yīng)為單例對(duì)象已經(jīng)確定,所以比較適合用于多線程環(huán)境中,多線程獲取單例對(duì)象不需要加鎖,可以有效的避免資源競(jìng)爭(zhēng),提高性能。
懶漢模式:
第一次要使用單例對(duì)象的時(shí)候再創(chuàng)建實(shí)例對(duì)象。如果單例對(duì)象構(gòu)造特別耗時(shí)或者浪費(fèi)資源(加載插件、加載網(wǎng)絡(luò)資源等),可以選擇懶漢模式,在第一次使用時(shí)才創(chuàng)建對(duì)象。
? ? ? ? 這里我們將會(huì)實(shí)現(xiàn)一種更加簡(jiǎn)便的單例模式,采用的是靜態(tài)局部變量的方式來(lái)創(chuàng)建對(duì)象,但要注意的是C++11之后,靜態(tài)變量才能在滿足線程安全的前提下唯一的被構(gòu)造和析構(gòu)。
餓漢模式實(shí)現(xiàn)及測(cè)試:
#include<iostream> //餓漢模式,將對(duì)象聲明為靜態(tài)私有成員變量 //程序啟動(dòng)就會(huì)創(chuàng)建出單例對(duì)象,該對(duì)象的構(gòu)造函數(shù)、析構(gòu)函數(shù)全部私有化,刪除拷貝構(gòu)造函數(shù) //保證了整個(gè)程序?qū)崿F(xiàn)過(guò)程中只會(huì)存在一個(gè)單例對(duì)象 //可以通過(guò)公共的接口去獲取該對(duì)象和該對(duì)象的數(shù)據(jù) //是一種以空間換時(shí)間的舉動(dòng),開始就創(chuàng)建好在后續(xù)使用時(shí)就不用耗費(fèi)時(shí)間去創(chuàng)建了 class Singleton { private: static Singleton _eton; Singleton():_data(1) { std::cout<<"單例對(duì)象構(gòu)造成功"<<std::endl; } ~Singleton() {} private: int _data; public: static Singleton& GetInstence() { return _eton; } int GetData() { return _data; } }; //類內(nèi)聲明的靜態(tài)成員變量要在類外定義,且要加上類域 Singleton Singleton::_eton; int main() { std::cout<<Singleton::GetInstence().GetData()<<std::endl; return 0; }
測(cè)試結(jié)果:
懶漢模式實(shí)現(xiàn)及測(cè)試:
#include<iostream> //懶漢模式:懶加載,其實(shí)是延遲加載的思想 //一個(gè)類在用的時(shí)候再實(shí)例化,在初始化構(gòu)造較為復(fù)雜的情況下 //使用懶漢思想就可以避免在不使用該類的情況下浪費(fèi)資源去構(gòu)造對(duì)象 #include<iostream> class Singleton { private: Singleton():_data(1) { std::cout<<"單例對(duì)象構(gòu)造成功"<<std::endl; } Singleton(const Singleton&)=delete; ~Singleton() {} private: int _data; public: static Singleton& GetInstence() { static Singleton _eton; return _eton; } int GetData() { return _data; } }; int main() { std::cout<<Singleton::GetInstence().GetData()<<std::endl; return 0; }
測(cè)試結(jié)果:
工廠模式
概念:
? ? ? ? 工廠模式是一種創(chuàng)建型設(shè)計(jì)模式,它提供了一種創(chuàng)建對(duì)象的最佳方式。在工廠模式中,我們創(chuàng)建對(duì)象時(shí)不會(huì)對(duì)上層暴露創(chuàng)建邏輯,而是通過(guò)使用一個(gè)共同結(jié)構(gòu)來(lái)指向新創(chuàng)建的對(duì)象,以此實(shí)現(xiàn)創(chuàng)建-使用的分離。
工廠模式分三種實(shí)現(xiàn)模式:簡(jiǎn)單工廠模式、工廠方法模式和抽象工廠模式
簡(jiǎn)單工廠模式:
簡(jiǎn)單工廠模式實(shí)現(xiàn)由一個(gè)工廠對(duì)象通過(guò)類型決定創(chuàng)建出來(lái)指定產(chǎn)品類的實(shí)例。假設(shè)有個(gè)工廠能生產(chǎn)水果,當(dāng)客戶需要產(chǎn)品時(shí)明確告知工廠生產(chǎn)哪類水果,工廠需要接收用戶提供的類別信息,當(dāng)新增產(chǎn)品的時(shí)候,工廠內(nèi)部去添加新產(chǎn)品的生產(chǎn)方式。
工廠方法模式:
在簡(jiǎn)單工廠模式下新增多個(gè)工廠,每個(gè)產(chǎn)品,對(duì)應(yīng)一個(gè)工廠,假設(shè)現(xiàn)在有A、B兩種產(chǎn)品,則設(shè)置兩個(gè)工廠,工廠A負(fù)責(zé)生產(chǎn)產(chǎn)品A,B負(fù)責(zé)生產(chǎn)產(chǎn)品B,用戶只知道產(chǎn)品的工廠名,而不知道具體產(chǎn)品的信息,工廠不需要再接收客戶的產(chǎn)品類別,而只負(fù)責(zé)生產(chǎn)產(chǎn)品。
抽象工廠模式:
工廠方法模式通過(guò)引入工廠等級(jí)結(jié)構(gòu),解決了簡(jiǎn)單工廠模式中工廠類職責(zé)太重的問(wèn)題,但由于工廠方法模式中的每一個(gè)工廠只生產(chǎn)一類產(chǎn)品,可能會(huì)導(dǎo)致系統(tǒng)中存在大量的工廠類,勢(shì)必會(huì)增加系統(tǒng)的開銷。此時(shí),我們可以考慮將一些先關(guān)的產(chǎn)品組成一個(gè)產(chǎn)品組(位于不同產(chǎn)品等級(jí)結(jié)構(gòu)中功能相關(guān)聯(lián)的產(chǎn)品組成家族),由同一個(gè)工廠來(lái)統(tǒng)一生產(chǎn),這就是抽象工廠模式的基本方式。
簡(jiǎn)單工廠模式實(shí)現(xiàn)及測(cè)試:
//簡(jiǎn)單工廠模式實(shí)現(xiàn)+測(cè)試 #include<iostream> #include<memory> //簡(jiǎn)單工廠模式通過(guò)參數(shù)控制可以生產(chǎn)任意產(chǎn)品 //思想簡(jiǎn)單粗暴,直觀,使用一個(gè)工廠生產(chǎn)同一等級(jí)結(jié)構(gòu)下的任意產(chǎn)品 //存在的問(wèn)題:1.所有東西都在一個(gè)工廠生產(chǎn),產(chǎn)品太多導(dǎo)致代碼量龐大 // 2.沒(méi)有很好的遵循開閉原則,新增產(chǎn)品就必須修改工廠方法 class Fruit { public: virtual void name()=0; }; class Apple:public Fruit { public: void name() override { std::cout<<"I'm Apple"<<std::endl; } }; class Banana:public Fruit { public: void name() override { std::cout<<"I'm Banana"<<std::endl; } }; class FruitFactory { public: static std::shared_ptr<Fruit> produce(const std::string &name) { if(name=="Apple") { return std::make_shared<Apple>(); } else { return std::make_shared<Banana>(); } } }; int main() { std::shared_ptr<Fruit> fruit=FruitFactory::produce("Apple"); fruit->name(); fruit=FruitFactory::produce("Banana"); fruit->name(); return 0; }
測(cè)試結(jié)果:
工廠方法模式實(shí)現(xiàn)及測(cè)試:
//工廠方法模式實(shí)現(xiàn)+測(cè)試 #include<iostream> #include<memory> //工廠方法模式定義一個(gè)創(chuàng)建對(duì)象的窗口,由子類決定創(chuàng)建哪種對(duì)象 //使用多個(gè)工廠分別生產(chǎn)指定的固定產(chǎn)品 //好處是減輕了工廠類的負(fù)擔(dān),將指定產(chǎn)品交規(guī)指定工廠來(lái)進(jìn)行生產(chǎn) //同時(shí)很好地遵循了開閉原則,增添新的產(chǎn)品只需要新增產(chǎn)品工廠即可,不需要修改原來(lái)的工廠類 //存在的問(wèn)題是:對(duì)于某種可以形成一組產(chǎn)品組的情況處理比較復(fù)雜,需要?jiǎng)?chuàng)建大量工廠類 class Fruit { public: virtual void name()=0; }; class Apple:public Fruit { public: void name() override { std::cout<<"I'm Apple"<<std::endl; } }; class Banana:public Fruit { public: void name() override { std::cout<<"I'm Banana"<<std::endl; } }; class FruitFactory { public: virtual std::shared_ptr<Fruit> produce()=0; }; class AppleFactory:public FruitFactory { public: std::shared_ptr<Fruit> produce() override { return std::make_shared<Apple>(); } }; class BananaFactory:public FruitFactory { public: std::shared_ptr<Fruit> produce() override { return std::make_shared<Banana>(); } }; int main() { std::shared_ptr<FruitFactory> ff(new AppleFactory()); std::shared_ptr<Fruit> fruit=ff->produce(); fruit->name(); ff.reset(new BananaFactory()); fruit=ff->produce(); fruit->name(); return 0; }
測(cè)試結(jié)果:
抽象工廠模式實(shí)現(xiàn)及測(cè)試:
//抽象工廠模式實(shí)現(xiàn)+測(cè)試 #include<iostream> #include<memory> //抽象工廠是圍繞一個(gè)超級(jí)工廠去創(chuàng)建其它工廠,每個(gè)生成的工廠按照工廠模式提供對(duì)象 //將工廠分成了抽象的兩層,抽象工廠和具體子工廠類,在工廠子類中生產(chǎn)不同類型的子產(chǎn)品 //抽象工廠模式適用于生產(chǎn)多個(gè)工廠系列產(chǎn)品衍生的設(shè)計(jì)模式,增加新的產(chǎn)品等級(jí)結(jié)構(gòu)復(fù)雜 //需要對(duì)原有系統(tǒng)進(jìn)行較大的修改,甚至需要修改抽象層代碼,因此也違背了“開閉原則” class Fruit { public: virtual void name()=0; }; class Apple:public Fruit { public: void name() override { std::cout<<"I'm Apple"<<std::endl; } }; class Banana:public Fruit { public: void name() override { std::cout<<"I'm Banana"<<std::endl; } }; class Ball { public: virtual void name()=0; }; class BasketBall:public Ball { public: void name() override { std::cout<<"I'm BasketBall"<<std::endl; } }; class FootBall:public Ball { public: void name() override { std::cout<<"I'm FootBall"<<std::endl; } }; class Factory { public: virtual std::shared_ptr<Fruit> GetFruit(const std::string &name)=0; virtual std::shared_ptr<Ball> GetBall(const std::string &name)=0; }; class FruitFactory :public Factory { public: std::shared_ptr<Ball> GetBall(const std::string &name) { return std::shared_ptr<Ball>(); } std::shared_ptr<Fruit> GetFruit(const std::string &name) { if(name=="Apple") { return std::make_shared<Apple>(); } else { return std::make_shared<Banana>(); } } }; class BallFactory :public Factory { public: std::shared_ptr<Fruit> GetFruit(const std::string &name) { return std::shared_ptr<Fruit>(); } std::shared_ptr<Ball> GetBall(const std::string &name) { if(name=="BasketBall") { return std::make_shared<BasketBall>(); } else { return std::make_shared<FootBall>(); } } }; class FactoryProducer { public: static std::shared_ptr<Factory> produce(const std::string &name) { if(name=="Fruit") { return std::make_shared<FruitFactory>(); } else { return std::make_shared<BallFactory>(); } } }; int main() { std::shared_ptr<Factory> factory =FactoryProducer::produce("Fruit"); std::shared_ptr<Fruit> fruit=factory->GetFruit("Apple"); fruit->name(); fruit=factory->GetFruit("Banana"); fruit->name(); std::shared_ptr<Factory> ff =FactoryProducer::produce("Ball"); std::shared_ptr<Ball> ball=ff->GetBall("BasketBall"); ball->name(); ball=ff->GetBall("FootBall"); ball->name(); return 0; }
測(cè)試結(jié)果:
建造者模式
概念:
建造者模式是一種創(chuàng)建型設(shè)計(jì)模式,使用多個(gè)簡(jiǎn)單的對(duì)象一步一步構(gòu)建成一個(gè)復(fù)雜的對(duì)象,能夠?qū)⒁粋€(gè)復(fù)雜的對(duì)象的構(gòu)建與它的表示分離,提供一種創(chuàng)建對(duì)象的最佳方式。主要用于解決對(duì)象過(guò)于復(fù)雜的問(wèn)題。
建造者模式主要基于五個(gè)核心類的實(shí)現(xiàn):
抽象產(chǎn)品類;
具體產(chǎn)品類:一個(gè)具體的產(chǎn)品類;
抽象Builder類:創(chuàng)建一個(gè)產(chǎn)品對(duì)象所需要各個(gè)部件的抽象接口;
具體產(chǎn)品的Builder類:實(shí)現(xiàn)抽象接口,構(gòu)建各個(gè)部件;
指揮者Director類:統(tǒng)一組建過(guò)程,提供給調(diào)用者使用,通過(guò)指揮者來(lái)獲取產(chǎn)品;
下面以生產(chǎn)建造一臺(tái)蘋果筆記本電腦為例來(lái)理解建造者模式:
建造者模式實(shí)現(xiàn)及測(cè)試:
//以建造模式構(gòu)建一臺(tái)蘋果筆記本電腦 //實(shí)現(xiàn)+測(cè)試 #include<iostream> #include<memory> //產(chǎn)品抽象類:電腦類 class Computer { public: Computer() {} void setBoard(const std::string &board) { _board=board; } void setDisplay(const std::string &display) { _display=display; } void showParamaters() { std::string param="Computer Paramaters:\n"; param+="\tBoard: "+_board+"\n"; param+="\tDisplay: "+_display+"\n"; param+="\tOs: "+_os+"\n"; std::cout<<param<<std::endl; } virtual void setOs()=0; protected: std::string _board;//主板 std::string _display;//顯示器 std::string _os;//操作系統(tǒng) }; //具體產(chǎn)品類:蘋果筆記本電腦類 class MacBook:public Computer { public: void setOs() override { _os="Mac OS x12"; } }; //抽象Builder類 class Builder { public: virtual void buildBoard(const std::string& board)=0; virtual void buildDisplay(const std::string& display)=0; virtual void buildOs()=0; virtual std::shared_ptr<Computer> build()=0; }; //MacBook Builder類 class MacBookBuilder:public Builder { public: MacBookBuilder():_computer(new MacBook()){} void buildBoard(const std::string& board) { _computer->setBoard(board); } void buildDisplay(const std::string& display) { _computer->setDisplay(display); } void buildOs() { _computer->setOs(); } std::shared_ptr<Computer> build() { return _computer; } private: std::shared_ptr<Computer> _computer; }; //Director類 class Director { public: Director(Builder *builder):_builder(builder) {} void construct (const std::string &board,const std::string &display) { _builder->buildBoard(board); _builder->buildDisplay(display); _builder->buildOs(); } private: std::shared_ptr<Builder> _builder; }; int main() { Builder * builder =new MacBookBuilder(); std::unique_ptr<Director> director(new Director(builder)); director->construct("惠普主板","華碩顯示器"); std::shared_ptr<Computer> computer=builder->build(); computer->showParamaters(); return 0; }
測(cè)試結(jié)果:
代理模式
概念:
代理模式是指代理控制對(duì)其它對(duì)象的訪問(wèn),也就是代理對(duì)象控制對(duì)原對(duì)象的引用。在某種情況下,一個(gè)對(duì)象不適合或者不能直接被引用訪問(wèn),而代理對(duì)象可以在客戶端和目標(biāo)對(duì)象之間起到中介的作用。
代理模式的結(jié)構(gòu)包括一個(gè)真正的你要訪問(wèn)的對(duì)象(目標(biāo)類)、一個(gè)是代理對(duì)象。目標(biāo)對(duì)象與代理對(duì)象實(shí)現(xiàn)同一個(gè)接口,先訪問(wèn)代理類再通過(guò)代理類訪問(wèn)目標(biāo)對(duì)象。
代理模式分為:靜態(tài)代理、動(dòng)態(tài)代理
靜態(tài)代理:
在編譯時(shí)就已經(jīng)確定好了代理類和被代理類的關(guān)系。也就是說(shuō)在編譯時(shí)就已經(jīng)確定了代理類要代理的是哪個(gè)被代理類。
動(dòng)態(tài)代理:
在運(yùn)行時(shí)才動(dòng)態(tài)生成代理類,并將其與被代理類綁定。這意味著,在運(yùn)行時(shí)才確定代理類要代理的是哪個(gè)被代理類。
下面只實(shí)現(xiàn)靜態(tài)代理:以租房為例,租客租房,中間經(jīng)過(guò)房屋中介向房東租房。
靜態(tài)代理模式實(shí)現(xiàn)及測(cè)試:
//代理模式實(shí)現(xiàn)+測(cè)試 //以租房子為例,房東構(gòu)建被代理類,中介構(gòu)建代理類 //租房子的時(shí)候直接找中介 #include<iostream> class RentHouse { public: virtual void renthouse()=0; }; class Landlord:public RentHouse { public: void renthouse() { std::cout<<"將房子租出去\n"; } }; class Intermediary:public RentHouse { public: void renthouse() { std::cout<<"發(fā)布租房告示\n"; std::cout<<"帶人看房\n"; _lanlord.renthouse(); std::cout<<"租后維修\n"; } private: Landlord _lanlord; }; int main() { Intermediary intermediary; intermediary.renthouse(); return 0; }
測(cè)試結(jié)果:
四、項(xiàng)目框架構(gòu)建
1.功能具象化
根據(jù)項(xiàng)目簡(jiǎn)介,我們簡(jiǎn)單總結(jié)日志系統(tǒng)的作用如下:
將一條消息,進(jìn)行格式化后形成指定格式的字符串后,寫入到指定位置
注意:
1.日志要寫入到指定的位置,指定位置包括:標(biāo)準(zhǔn)輸出、指定文件、滾動(dòng)文件且支持將日志消息落地到不同位置,即支持多落地方向。
2.日志寫入支持不同的寫入方式:同步寫入和異步寫入
同步寫入:業(yè)務(wù)線程自己負(fù)責(zé)日志的寫入
異步寫入:業(yè)務(wù)線程將日志放入緩沖區(qū)內(nèi)存,讓其它異步線程負(fù)責(zé)將日志寫入指定位置
3.日志輸出以日志器為單位,支持多日志器(使不同的項(xiàng)目組有不同的輸出策略)
2.模塊劃分
1.日志等級(jí)模塊:枚舉出日志分為多少個(gè)等級(jí),對(duì)不同的日志等級(jí)有不同的標(biāo)記--以便控制輸出。
2.日志消息模塊:封裝一條日志所需要的各種要素(時(shí)間,線程ID,文件名,行號(hào),日志等級(jí),消息主體等...)。
3.消息格式化模塊:按照指定的格式,對(duì)日志消息關(guān)鍵要素進(jìn)行組織,最終得到一個(gè)指定格式的字符串。
例:
[%d{%H%M%S}]%T[%t]%T[%p]%T[%c]%T%f:%l%T%m%n
[09:18:44]? ? ? ? [98765]? ? ? ? [FATAL]? ? ? ? [root]? ? ? ? main.c:166? ? 段錯(cuò)誤...\n
4.日志落地模塊負(fù)責(zé)對(duì)日志消息進(jìn)行指定方向的寫入輸出,用工廠模式實(shí)現(xiàn)。
5.日志器模塊:對(duì)以上模塊的整合,用建造者模式實(shí)現(xiàn)。
日志限制輸出等級(jí),消息格式化模塊對(duì)象,日志落地模塊對(duì)象,同步日志器模塊,異步日志器模塊
6.異步線程模塊:實(shí)際運(yùn)用代理模式。
7.單例的日志器管理模塊:對(duì)日志進(jìn)行全局管理,以便能夠在項(xiàng)目的任何位置獲取指定的日志器進(jìn)行日志輸出。
五、項(xiàng)目編寫
1.實(shí)用工具類模塊的設(shè)計(jì)+測(cè)試
概念:
實(shí)用工具類用于提前完成一些零碎功能接口,以便于項(xiàng)目中使用,本項(xiàng)目需要的功能如下:
- 獲取系統(tǒng)時(shí)間
- 判斷文件路徑是否存在
- 獲取文件的所在路徑
- 創(chuàng)建目錄
實(shí)現(xiàn):
實(shí)現(xiàn)代碼:util.hpp
//使用宏定義,防止頭文件被重復(fù)包含 #ifndef __M_UTIL_H__ #define __M_UTIL_H__ #include<iostream> #include<ctime> #include<sys/stat.h> #include <sys/types.h> namespace wz_logs { namespace util { //有獲取系統(tǒng)時(shí)間的需求,因此設(shè)置Time類 class Time { public: //定義為靜態(tài)成員函數(shù)是為了方便訪問(wèn) //靜態(tài)成員函數(shù)屬于整個(gè)類,后續(xù)在項(xiàng)目全局都可以直接通過(guò)類名訪問(wèn)而不用再定義對(duì)象訪問(wèn) static size_t GetTime() { return (size_t)time(nullptr); } //本質(zhì)就是對(duì)獲取時(shí)間戳的一個(gè)函數(shù)的封裝 }; //剩下無(wú)論是判斷文件路徑是否存在,判斷文件所在路徑,還是創(chuàng)建目錄都是和文件有關(guān)的操作 //因此設(shè)置File類 class File { public: //判斷文件是否存在 static bool Exist(const std::string &pathname) { //stat 函數(shù)的功能是獲取文件的屬性,成功返回0,失敗返回-1 //這個(gè)系統(tǒng)調(diào)用函數(shù)在Linux和Windows都適用 //Linux下還可以用access(pathname,F_OK),存在返回0,不存在返回1 struct stat st; if(stat(pathname.c_str(),&st)<0) { return false; } return true; } //獲取文件所在目錄[獲取當(dāng)前指定路徑文件的父目錄](méi) static std::string Path(const std::string &pathname) { size_t pos=pathname.find_last_of("/\\"); if(pos==std::string::npos) return "."; return pathname.substr(0,pos+1); } //創(chuàng)建目錄,有就跳過(guò),沒(méi)有就創(chuàng)建 static void CreatDirectory(const std::string &pathname) { //一定要記得初始化 size_t pos=0,idx=0; while(idx<pathname.size()) { //從頭開始找目錄分割標(biāo)識(shí)符 pos=pathname.find_first_of("/\\",idx); //如果沒(méi)找到,就創(chuàng)建當(dāng)前路徑 if(pos==std::string::npos) { mkdir(pathname.c_str(),0777); break; } //找到了就依次看父級(jí)目錄是否存在,如果存在,就直接跳過(guò)本次,不存在就順便創(chuàng)建 std::string parent_dir=pathname.substr(0,pos+1); if(Exist(parent_dir)==true) { idx=pos+1; continue; } mkdir(parent_dir.c_str(),0777); idx=pos+1; } } }; } } #endif
測(cè)試代碼:test.cpp
#include "util.hpp" int main() { std::cout<<wz_logs::util::Time::GetTime()<<std::endl; std::string pathname="./abc/def/ksh/wz.txt"; wz_logs::util::File::CreatDirectory(pathname); std::cout<<wz_logs::util::File::Exist("./abc/def")<<std::endl; std::cout<<wz_logs::util::File::Exist("./Abc/def")<<std::endl; wz_logs::util::File::CreatDirectory("./abc/def/llp"); std::cout<<wz_logs::util::File::Path(pathname)<<std::endl; return 0; }
測(cè)試結(jié)果:
2.日志等級(jí)類模塊的設(shè)計(jì)+測(cè)試
概念:
日志等級(jí)一般包括7個(gè)等級(jí),分別為:
- UNKNOW 未知
- OFF 關(guān)閉所有日志輸出
- DEBUG進(jìn)行調(diào)試時(shí)候打印日志
- INFO打印一些用戶提示信息
- WARN打印警告信息
- ERROR打印錯(cuò)誤信息
- FATAL打印致命信息-導(dǎo)致程序崩潰的信息
? ? ? ?每一個(gè)項(xiàng)目中都會(huì)設(shè)置一個(gè)默認(rèn)的日志輸出等級(jí),只有輸出的日志等級(jí)大于或等于默認(rèn)限制等級(jí)的時(shí)候才可以進(jìn)行輸出,由此我們的日志等級(jí)類模塊需要包含兩個(gè)部分,一部分為一個(gè)枚舉變量,包含所有日志等級(jí),另一個(gè)是將對(duì)應(yīng)等級(jí)的枚舉轉(zhuǎn)換為一個(gè)對(duì)應(yīng)的字符串。
實(shí)現(xiàn):
#ifndef __M_LEVEL_H__ #define __M_LEVEL_H__ #include<iostream> namespace wz_logs { class LogLevel { public: enum class value//enum class 是C++11之后 { UNKNOW=0, DEBUG, INFO, WARN, ERROR, FATAL, OFF }; static char* ToString(LogLevel::value level) { switch(level) { case LogLevel::value::DEBUG: return "DEBUG"; case LogLevel::value::INFO: return "INFO"; case LogLevel::value::WARN: return "WARN"; case LogLevel::value::ERROR: return "ERROR"; case LogLevel::value::FATAL: return "FATAL"; case LogLevel::value::OFF: return "OFF"; } return "UNKNOW"; } }; } #endif
因代碼很簡(jiǎn)單且需要配合其它模塊使用因此,本模塊的測(cè)試放到后面進(jìn)行。
3.日志消息類模塊的設(shè)計(jì)+測(cè)試
概念:
需要包括輸出的一條實(shí)際的日志消息所需要的各項(xiàng)內(nèi)容,分別為:
- 日志的輸出時(shí)間
- 日志等級(jí)
- 源文件名稱
- 源文件行號(hào)
- 線程ID
- 日主體消息
- 日志器名稱
想要獲取日志消息時(shí)先傳入需要的參數(shù)定義消息對(duì)象,再對(duì)這個(gè)消息對(duì)象做輸出處理
實(shí)現(xiàn):
#ifndef __M_MSG_H__ #define __M_MSG_H__ #include<iostream> #include<string> #include<thread> #include"level.hpp" namespace wz_logs { struct LogMsg { time_t _ctime;//日志產(chǎn)生的時(shí)間戳 LogLevel::value _level;//日志等級(jí) size_t _line;//行號(hào) std::thread::id _tid;//線程id std::string _file;//源文件名稱 std::string _logger;//日志器名稱 std::string _payload;//有效信息載荷 //以上成員變量都在構(gòu)造函數(shù)的初始化列表完成初始化 LogMsg(LogLevel::value _level, size_t line, const std::string file, const std::string logger, const std::string msg): _ctime(util::GetTime()), _level(level), _line(line), _tid(std::this_thread::get_id()), _file(file), _logger(logger) _payload(msg){} }; } #endif
同樣本模塊需要同其它模塊結(jié)合測(cè)試。
4.日志格式化類模塊的設(shè)計(jì)+測(cè)試
概念:
該模塊是本項(xiàng)目較為復(fù)雜且重要的部分,我將從宏觀到微觀,從整體結(jié)構(gòu)到細(xì)節(jié)依次總結(jié):
? ? ? ?日志格式化是指將日志消息組織成指定格式的字符串,因此日志格式化類中至少應(yīng)該包括兩個(gè)成員變量,一是用于描述組織形式的格式化字符串(可以看做是一個(gè)規(guī)則),另一個(gè)是用于保存根據(jù)格式化字符串取出的日志消息類中對(duì)應(yīng)消息的一個(gè)數(shù)據(jù)結(jié)構(gòu),由于我們的消息中有日期信息這類包含子項(xiàng)[時(shí)、分、秒]的信息,因此這里用的是一個(gè)格式化子項(xiàng)數(shù)組,數(shù)組中應(yīng)當(dāng)存儲(chǔ)依次存儲(chǔ)的是解析格式化字符串后依次取出的子項(xiàng)內(nèi)容轉(zhuǎn)成的字符串。
不同格式的格式化子項(xiàng):
以abc[%d{%H%M%S}][%f:%l]%m%n為例,依次列出格式化子項(xiàng)如下:
1.其他信息(指非格式化信息)子項(xiàng)->abc[
2.日期子項(xiàng)->%H%M%S
3.其它信息子項(xiàng)->]
4.其它信息子項(xiàng)->[
5.文件名子項(xiàng)->無(wú)
6.其它信息子項(xiàng)->:
7.行號(hào)子項(xiàng)->無(wú)
8.其它信息子項(xiàng)->]
9.消息主體子項(xiàng)->無(wú)
10.換行子項(xiàng)->無(wú)
有了上述分析,我們想實(shí)現(xiàn)一個(gè)日志格式化模塊,需包括以下具體內(nèi)容
1.首先要實(shí)現(xiàn)一個(gè)格式化子項(xiàng)類,用于把不同的格式化子項(xiàng)內(nèi)容從日志信息模塊中取出后輸出;
2.其次在日志格式化類內(nèi)部,因?yàn)槲覀兊母袷交址亲远x的,因此在對(duì)msg進(jìn)行格式化之前,需要先檢查合法性,即要定義一個(gè)檢查合法性的接口;格式化字符串沒(méi)問(wèn)題了,接下來(lái)就是提供對(duì)msg進(jìn)行格式化的接口;格式化的整個(gè)過(guò)程可以分成對(duì)不同格式化子項(xiàng)的格式化,因此還須提供創(chuàng)建格式化子類對(duì)象的接口。
實(shí)現(xiàn):
實(shí)現(xiàn)代碼:formatter.hpp
#ifndef __M_FMT_H__ #define __M_FMT_H__ #include<iostream> #include<cassert> #include<vector> #include<string> #include<memory> #include <sstream> #include<utility> namespace wz_logs { //首先實(shí)現(xiàn)一個(gè)格式化子項(xiàng)類 //實(shí)際上用的是多態(tài)的思想,ptrx相當(dāng)于一個(gè)父類指針,當(dāng)調(diào)用重寫過(guò)的format函數(shù) //就可以實(shí)現(xiàn)不同格式化子項(xiàng)對(duì)象調(diào)用相應(yīng)的format函數(shù),實(shí)現(xiàn)對(duì)應(yīng)內(nèi)容的提取及格式化 //1.抽象格式化子項(xiàng)基類 class FormatItem { public: //定義一個(gè)指向基類的智能指針 using ptr =std::shared_ptr<FormatItem>; virtual void format(std::ostream &out,LogMsg &msg)=0; }; //派生格式化子項(xiàng)子類--消息,等級(jí),時(shí)間,文件名。行號(hào)... class MsgFormatItem:public FormatItem{ public: void format(std::ostream &out,LogMsg &msg) override { out<<msg._payload; } }; class LevelFormatItem:public FormatItem{ public: void format(std::ostream &out,LogMsg &msg) override { out<<LogLevel::ToString(msg._level); } }; class TimeFormatItem:public FormatItem{ public: TimeFormatItem(const std::string &fmt="%H:%M:%S"):_time_fmt(fmt) {} void format(std::ostream &out,LogMsg &msg) override { struct tm t; localtime_r(&msg._ctime,&t); char tmp[32]={0}; strftime(tmp,31,_time_fmt.c_str(),&t); out<<tmp; } private: std::string _time_fmt;//"%H:%M:%S" }; class FileFormatItem:public FormatItem{ public: void format(std::ostream &out,LogMsg &msg) override { out<<msg._file; } }; class LineFormatItem:public FormatItem{ public: void format(std::ostream &out,LogMsg &msg) override { out<<msg._line; } }; class ThreadFormatItem:public FormatItem{ public: void format(std::ostream &out,LogMsg &msg) override { out<<msg._tid; } }; class LoggerFormatItem:public FormatItem{ public: void format(std::ostream &out,LogMsg &msg) override { out<<msg._logger; } }; class TabFormatItem:public FormatItem{ public: void format(std::ostream &out,LogMsg &msg) override { out<<"\t"; } }; class NlineFormatItem:public FormatItem{ public: void format(std::ostream &out,LogMsg &msg) override { out<<"\n"; } }; class OtherFormatItem:public FormatItem{ public: OtherFormatItem(const std::string &str):_str(str) {} void format(std::ostream &out,LogMsg &msg) override { out<<_str; } private: std::string _str; }; class Formatter { public: Formatter(const std::string &pattern="[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n") :_pattern(pattern) { assert(ParsePattern()); } //對(duì)msg進(jìn)行格式化 void format(std::ostream &out,LogMsg &msg) { for(auto &item:_items) { item->format(out,msg); } } std::string format(LogMsg &msg) { std::stringstream ss; format(ss,msg); return ss.str(); } private: //對(duì)格式化字符串的合法性進(jìn)行解析 bool ParsePattern() { std::vector<std::pair<std::string,std::string>> fmt_order; size_t pos=0; std::string key,val; while(pos<_pattern.size()) { //如果不是%,表示是普通字符 if(_pattern[pos]!='%') { val.push_back(_pattern[pos++]); continue; } //走到這里說(shuō)明就遇到%了,下一個(gè)是不是格式化字符,取決于下一個(gè)是不是% if(pos+1<_pattern.size()&&_pattern[pos+1]=='%') { val.push_back('%'); pos+=2; continue; } //原始字符串處理完畢,可以添加到fmt_order數(shù)組中了 if(val.empty()==false) { fmt_order.push_back(std::make_pair("",val)); val.clear(); } //走到這里說(shuō)明此時(shí)pos指向一個(gè)百分號(hào)且下一個(gè)是格式化字符 pos+=1; if(pos==_pattern.size()) { std::cout<<"'%'之后沒(méi)有格式化字符..."<<std::endl; return false; } key=_pattern[pos]; pos+=1; if(pos<_pattern.size()&&_pattern[pos]=='{') { pos+=1; while(pos<_pattern.size()&&_pattern[pos]!='}') { val.push_back(_pattern[pos++]); } //如果走到了末尾還沒(méi)遇到'}',則說(shuō)明出錯(cuò)了 if(pos==_pattern.size()) { std::cout<<"子規(guī)則{}匹配錯(cuò)誤..."<<std::endl; return false; } pos+=1; } fmt_order.push_back(std::make_pair(key,val)); key.clear(); val.clear(); } //2.根據(jù)解析得到的數(shù)據(jù)初始化格式化子項(xiàng)數(shù)組成員 for(auto &it:fmt_order) { _items.push_back(creamItem(it.first,it.second)); } return true; } //根據(jù)不同的格式化字符創(chuàng)建不同的格式化子項(xiàng)對(duì)象 FormatItem::ptr creamItem(const std::string &key,const std::string &val) { if(key=="d") return std::make_shared<TimeFormatItem>(val); if(key=="t") return std::make_shared<ThreadFormatItem>(); if(key=="c") return std::make_shared<LoggerFormatItem>(); if(key=="f") return std::make_shared<FileFormatItem>(); if(key=="l") return std::make_shared<LineFormatItem>(); if(key=="p") return std::make_shared<LevelFormatItem>(); if(key=="T") return std::make_shared<TabFormatItem>(); if(key=="n") return std::make_shared<NlineFormatItem>(); if(key=="m") return std::make_shared<MsgFormatItem>(); return std::make_shared<OtherFormatItem>(val); } std::string _pattern; std::vector<FormatItem::ptr> _items; }; } #endif
測(cè)試代碼:test.cpp
//formatter.hpp功能測(cè)試 #include "util.hpp" #include"message.hpp" #include"level.hpp" #include"formatter.hpp" int main() { //注意分開測(cè)試每組情況,一起測(cè)會(huì)出問(wèn)題 //普通測(cè)試 wz_logs::LogMsg msg(wz_logs::LogLevel::value::INFO,66,"main.c","root","測(cè)試..."); wz_logs::Formatter fmt; std::cout<<fmt.format(msg)<<std::endl; //邊界情況測(cè)試1,%%問(wèn)題 wz_logs::LogMsg msg1(wz_logs::LogLevel::value::INFO,66,"main.c","root","測(cè)試..."); wz_logs::Formatter fmt1("[%d{%%%H:%M}][%t][%c][%f:%l][%p]%T%m%n"); std::cout<<fmt1.format(msg1)<<std::endl; // //邊界情況測(cè)試2,%后無(wú)格式化字符問(wèn)題 // wz_logs::LogMsg msg2(wz_logs::LogLevel::value::INFO,66,"main.c","root","測(cè)試..."); // wz_logs::Formatter fmt2("[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%"); // std::cout<<fmt2.format(msg2)<<std::endl; // //邊界情況測(cè)試3,{}匹配問(wèn)題 // wz_logs::LogMsg msg3(wz_logs::LogLevel::value::INFO,66,"main.c","root","測(cè)試..."); // wz_logs::Formatter fmt3("[%d{%H:%M:%S][%t][%c][%f:%l][%p]%T%m%n"); // std::cout<<fmt3.format(msg3)<<std::endl; // //邊界情況測(cè)試4,沒(méi)定義的格式化字符%g問(wèn)題 // wz_logs::LogMsg msg4(wz_logs::LogLevel::value::INFO,66,"main.c","root","測(cè)試..."); // wz_logs::Formatter fmt4("[%d{%H:%M:%S}][%t][%c][%g%g%g:%l][%p]%T%m%n"); // std::cout<<fmt4.format(msg4)<<std::endl; // return 0; }
測(cè)試結(jié)果:其中順便完成了對(duì)日志等級(jí)和日志消息模塊的測(cè)試
5.落地類模塊的設(shè)計(jì)+測(cè)試
概念:
? ? ? ?日志落地類主要負(fù)責(zé)落地日志消息到目的地,即將格式化完成后的日志消息格式化字符串,輸出到指定的位置,擴(kuò)展支持同時(shí)將日志落地到不同的位置(用簡(jiǎn)單工廠模式來(lái)實(shí)現(xiàn)),具體的我們要實(shí)現(xiàn)的落地方向有三個(gè):標(biāo)準(zhǔn)輸出,指定文件,滾動(dòng)文件。
實(shí)現(xiàn)思想:
1.抽象出落地模塊類
2.不同落地方向從基類進(jìn)行派生
3.使用工廠模式進(jìn)行創(chuàng)建與表示分離
實(shí)現(xiàn):
實(shí)現(xiàn)代碼:sink.hpp
#ifndef __M_SINK_H__ #define __M_SINK_H__ #include<iostream> #include<memory> #include<fstream> #include<sstream> #include"util.hpp" #include<cassert> namespace wz_logs { //抽象一個(gè)落地基類 class LogSink { public: using ptr=std::shared_ptr<LogSink>; LogSink() {} virtual ~LogSink() {} virtual void sink(const char *data,size_t len)=0; }; //落地到標(biāo)準(zhǔn)輸出 class StdoutSink:public LogSink { public: //將日志消息寫入到標(biāo)準(zhǔn)輸出 void sink(const char *data,size_t len) { //不能用cout<<直接輸出,因?yàn)檫@種沒(méi)辦法指定大小,通常是遇到'\0'截止 std::cout.write(data,len); } }; //落地到指定文件 class FileSink:public LogSink { public: //為了提高操作效率,在構(gòu)造落地項(xiàng)時(shí)就打開文件,此時(shí)需要傳入文件名 FileSink(const std::string &pathname):_pathname(pathname) { //1.創(chuàng)建文件所在的目錄 util::File::CreatDirectory(util::File::Path(pathname)); //2.創(chuàng)建并打開文件,以二進(jìn)制可追加的形式打開,符合文件寫入的條件 _ofs.open(_pathname,std::ios::binary|std::ios::app); //文件打開才能進(jìn)行后續(xù)寫入,因此加個(gè)斷言 assert(_ofs.is_open()); } //將日志消息寫入到指定文件 void sink(const char *data,size_t len) { _ofs.write(data,len); //加個(gè)斷言,判斷操作句柄是否正常,保證寫入正常才能繼續(xù)運(yùn)行 assert(_ofs.good()); } private: std::string _pathname; std::ofstream _ofs; }; //落地到滾動(dòng)文件 class RollBySizeSink:public LogSink { public: //為了提高操作效率,在構(gòu)造落地項(xiàng)時(shí)就打開文件,此時(shí)需要傳入文件名 //另外也要設(shè)置滾動(dòng)文件中最大數(shù)據(jù)寫入量 RollBySizeSink(const std::string &basename,size_t max_fsize) :_basename(basename), _max_fsize(max_fsize),_cur_fsize(0),_count(1) { //1.創(chuàng)建文件所在的目錄 std::string pathname=CreatNewFile(); util::File::CreatDirectory(util::File::Path(pathname)); //2.創(chuàng)建并打開文件,以二進(jìn)制可追加的形式打開,符合文件寫入的條件 _ofs.open(pathname,std::ios::binary|std::ios::app); //文件打開才能進(jìn)行后續(xù)寫入,因此加個(gè)斷言 assert(_ofs.is_open()); } //將日志消息寫入到滾動(dòng) void sink(const char *data,size_t len) { if(_cur_fsize>=_max_fsize) { _ofs.close();//關(guān)閉原來(lái)打開的文件 std::string pathname=CreatNewFile(); _ofs.open(pathname,std::ios::binary|std::ios::app); assert(_ofs.is_open()); _cur_fsize=0;//每次新建一個(gè)滾動(dòng)文件,當(dāng)前大小都要清空 } _ofs.write(data,len); assert(_ofs.good()); _cur_fsize+=len; } private: //進(jìn)行大小判斷,超過(guò)指定大小,創(chuàng)建新文件 std::string CreatNewFile() { //獲取系統(tǒng)時(shí)間,以時(shí)間來(lái)構(gòu)造文件拓展名 time_t t=util::Time::GetTime(); struct tm lt; localtime_r(&t,<); std::stringstream filename; filename<<lt.tm_year+1900; filename<<lt.tm_mon+1; filename<<lt.tm_mday; filename<<lt.tm_hour; filename<<lt.tm_min; filename<<lt.tm_sec; filename<<"-"; filename<<_count++;//為了區(qū)分因?qū)懭脒^(guò)快導(dǎo)致的一秒內(nèi)產(chǎn)生的多個(gè)滾動(dòng)文件 filename<<".log"; return filename.str(); } private: //文件基礎(chǔ)名,一個(gè)系列的滾動(dòng)文件擁有共同的基礎(chǔ)名+各自的擴(kuò)展名 std::string _basename; std::ofstream _ofs; //用于記錄滾動(dòng)文件的最大大小,每次文件存放數(shù)據(jù)量達(dá)到最大,就要切換文件 size_t _max_fsize; //用于記錄當(dāng)前文件已經(jīng)寫入的大小,后續(xù)比較時(shí)就不用每次獲取文件屬性,提高了效率 size_t _cur_fsize; size_t _count; }; //不定參函數(shù) class SinkFactory{ public: //因?yàn)椴煌穆涞胤较蛐枰覀儌魅氲膮?shù)類型和數(shù)量不確定,因此這里用不定參函數(shù) template <typename SinkType,typename ...Args> static LogSink::ptr creat(Args &&...args) { return std::make_shared<SinkType>(std::forward<Args>(args)...); } }; } #endif
測(cè)試代碼:test.cpp
//sink.hpp功能測(cè)試 #include "util.hpp" #include"message.hpp" #include"level.hpp" #include"formatter.hpp" #include"sink.hpp" int main() { //普通測(cè)試 wz_logs::LogMsg msg(wz_logs::LogLevel::value::INFO,66,"main.c","root","測(cè)試..."); wz_logs::Formatter fmt; std::string str=fmt.format(msg); wz_logs::LogSink::ptr stdout_lsp=wz_logs::SinkFactory::creat<wz_logs::StdoutSink>(); wz_logs::LogSink::ptr file_lsp=wz_logs::SinkFactory::creat<wz_logs::FileSink>("./logfile/test.log"); wz_logs::LogSink::ptr roll_lsp=wz_logs::SinkFactory::creat<wz_logs::RollBySizeSink>("./logfile/roll-",1024*1024); stdout_lsp->sink(str.c_str(),str.size()); file_lsp->sink(str.c_str(),str.size()); size_t cursize=0; size_t count=0; while(cursize<1024*1024*10) { std::string tmp=str+std::to_string(count++); roll_lsp->sink(tmp.c_str(),tmp.size()); cursize+=tmp.size(); } return 0; }
測(cè)試結(jié)果:
擴(kuò)展模塊:
用于檢查是否支持使用用戶自己定義的落地方向
我們自己定義以時(shí)間為切換標(biāo)準(zhǔn)的滾動(dòng)文件 ,來(lái)測(cè)試擴(kuò)展功能,如下:
//test.cpp,直接在測(cè)試文件中去模擬用戶使用時(shí)自定義落地派生類 //實(shí)現(xiàn) //定義一個(gè)枚舉類,用戶使用時(shí)只需要傳入想要的枚舉變量即可,方便用戶使用 enum class TIMEGAP { GAP_SEC, GAP_MIN, GAP_HOUR, GAP_DAY }; //擴(kuò)展一個(gè)落地方向?yàn)橐詴r(shí)間為切換條件的滾動(dòng)文件的派生類 class RollByTimeSink:public wz_logs::LogSink { public: RollByTimeSink(const std::string &basename,TIMEGAP gap_type) :_basename(basename),_cur_gap(0) { switch(gap_type) { case TIMEGAP::GAP_SEC: _gap_size=1; break; case TIMEGAP::GAP_MIN: _gap_size=60; break; case TIMEGAP::GAP_HOUR: _gap_size=3600; break; case TIMEGAP::GAP_DAY: _gap_size=3600*24; break; } _cur_gap=_gap_size==1?wz_logs::util::Time::GetTime():wz_logs::util::Time::GetTime()%_gap_size; //1.創(chuàng)建文件及文件所在的目錄 std::string pathname=CreatNewFile(); wz_logs::util::File::CreatDirectory(wz_logs::util::File::Path(pathname)); //2.打開文件,以二進(jìn)制可追加的形式打開,符合文件寫入的條件 _ofs.open(pathname,std::ios::binary|std::ios::app); //文件打開才能進(jìn)行后續(xù)寫入,因此加個(gè)斷言 assert(_ofs.is_open()); } //判斷當(dāng)前文件的_cur_gap是否是當(dāng)前時(shí)間段,若不是,則要切換文件 void sink(const char *data,size_t len) { time_t cur=wz_logs::util::Time::GetTime(); if((cur%_gap_size)!=_cur_gap) { _ofs.close();//關(guān)閉原來(lái)打開的文件 std::string pathname=CreatNewFile(); _ofs.open(pathname,std::ios::binary|std::ios::app); assert(_ofs.is_open()); } _ofs.write(data,len); assert(_ofs.good()); } private: //進(jìn)行大小判斷,超過(guò)指定大小,創(chuàng)建新文件 std::string CreatNewFile() { //獲取系統(tǒng)時(shí)間,以時(shí)間來(lái)構(gòu)造文件拓展名 time_t t=wz_logs::util::Time::GetTime(); struct tm lt; localtime_r(&t,<); std::stringstream filename; filename<<lt.tm_year+1900; filename<<lt.tm_mon+1; filename<<lt.tm_mday; filename<<lt.tm_hour; filename<<lt.tm_min; filename<<lt.tm_sec; filename<<".log"; return filename.str(); } private: //文件基礎(chǔ)名,一個(gè)系列的滾動(dòng)文件擁有共同的基礎(chǔ)名+各自的擴(kuò)展名 std::string _basename; std::ofstream _ofs; //用于記錄當(dāng)前的時(shí)間段 size_t _cur_gap; //用于記錄規(guī)定文件切換的時(shí)間段長(zhǎng)度,讓用戶自定義傳入 size_t _gap_size; }; //測(cè)試 int main() { //RollByTimeSink測(cè)試 wz_logs::LogMsg msg(wz_logs::LogLevel::value::INFO,66,"main.c","root","測(cè)試..."); wz_logs::Formatter fmt; std::string str=fmt.format(msg); wz_logs::LogSink::ptr rollbytime_lsp=wz_logs::SinkFactory::creat<RollByTimeSink>("./logfile/roll-",TIMEGAP::GAP_SEC); time_t old=wz_logs::util::Time::GetTime(); while(wz_logs::util::Time::GetTime()<old+5) { rollbytime_lsp->sink(str.c_str(),str.size()); } return 0; }
測(cè)試結(jié)果:
6.日志器模塊的設(shè)計(jì)+測(cè)試
概念:
? ? ?日志器主要是用來(lái)和前端交互,當(dāng)我們需要使用日志系統(tǒng)打印log時(shí),只要?jiǎng)?chuàng)建logger對(duì)象,調(diào)用該對(duì)象不同等級(jí)的日志輸出方法接口,就可以輸出想輸出的日志消息,支持解析可變參數(shù)列表和輸出格式,即可以做到像使用prntf()函數(shù)一樣打印日志。
當(dāng)前日志系統(tǒng)支持同步日志和異步日志,兩個(gè)不同的日志器唯一不同的地方在于他們?cè)谌罩镜穆涞胤绞缴嫌兴煌?/strong>
同步日志器:直接對(duì)日志消息進(jìn)行輸出
異步日志器:將日志消息放入到緩沖區(qū)中,有異步線程進(jìn)行輸出
因此日志器類在設(shè)計(jì)的時(shí)候應(yīng)先設(shè)計(jì)出一個(gè)Lgger基類,在Logger基類的基礎(chǔ)上,繼承出同步日志器(Synclogger)和異步日志器(AsynLogger)。
另外,日志器模塊可以看做是前面多個(gè)模塊的整合,創(chuàng)建一個(gè)日志器,需要管理的對(duì)象及提供的方法分別如下:
管理的成員:
1.格式化模塊對(duì)象
2.落地模塊對(duì)象數(shù)組(一個(gè)日志器可能會(huì)向多個(gè)位置進(jìn)行輸出?)
3.默認(rèn)的日志輸出限制等級(jí)(大于等于限制等級(jí)的日志才可以輸出)
4.互斥鎖(保證日志輸出過(guò)程是線程安全的,不會(huì)出現(xiàn)交叉日志)
5.日志器名稱(日志器唯一標(biāo)識(shí),以便于查找)
提供的操作:
1.debug等級(jí)日志的輸出操作
2.info等級(jí)日志的輸出操作
3.warn等級(jí)日志的輸出操作
4.error等級(jí)日志的輸出操作
5.fatal等級(jí)日志的輸出操作
其中每種輸出操作中都分別會(huì)封裝日志消息LogMsg,各個(gè)接口日志等級(jí)不同
由于整個(gè)日志器管理的模塊較多且操作較為復(fù)雜,故而日志器模塊的實(shí)現(xiàn)我們采用建造者模式來(lái)實(shí)現(xiàn),模塊關(guān)聯(lián)中使用基類指針對(duì)子類日志器對(duì)象進(jìn)行日志管理和操作。
日志器基類+同步日志器的實(shí)現(xiàn):
先來(lái)實(shí)現(xiàn)日志器基類和同步日志器并進(jìn)行功能測(cè)試
實(shí)現(xiàn)代碼:logger.hpp
#ifndef __M_LOGGER_H__ #define __M_LOGGER_H__ #include"util.hpp" #include"level.hpp" #include"formatter.hpp" #include"sink.hpp" #include"message.hpp" #include<atomic> #include<mutex> #include<stdio.h> #include<stdarg.h> #include<stdlib.h> namespace wz_logs { class Logger { public: Logger( const std::string &logger_name, Formatter::ptr &formatter, std::vector<LogSink::ptr> &sinks, LogLevel::value &level): _logger_name(logger_name), _formatter(formatter), _limit_level(level), _sinks(sinks.begin(),sinks.end()) {} //需要向外提供的是一系列對(duì)不同等級(jí)日志消息輸出的方法 using ptr=std::shared_ptr<Logger>; void debug(const std::string &file,size_t line,const char* fmt,...) { //通過(guò)傳入的參數(shù)構(gòu)造出一個(gè)日志消息對(duì)象,進(jìn)行格式化,最終落地 //1.判斷當(dāng)前的日志輸出等級(jí)是否達(dá)到輸出等級(jí) if(LogLevel::value::DEBUG<_limit_level) {return ;} //對(duì)fmt格式化字符串和不定參進(jìn)行字符串組織,得到日志消息字符串 va_list ap; va_start(ap,fmt); char* res; int ret=vasprintf(&res,fmt,ap); if(ret==-1) { std::cout<<"vasprintf failed!\n"; return; } va_end(ap); serialize(LogLevel::value::DEBUG,file,line,res); free(res); } void info(const std::string &file,size_t line,const char*fmt,...) { //通過(guò)傳入的參數(shù)構(gòu)造出一個(gè)日志消息對(duì)象,進(jìn)行格式化,最終落地 //1.判斷當(dāng)前的日志輸出等級(jí)是否達(dá)到輸出等級(jí) if(LogLevel::value::INFO<_limit_level) {return ;} //對(duì)fmt格式化字符串和不定參進(jìn)行字符串組織,得到日志消息字符串 va_list ap; va_start(ap,fmt); char* res; int ret=vasprintf(&res,fmt,ap); if(ret==-1) { std::cout<<"vasprintf failed!\n"; return; } va_end(ap); serialize(LogLevel::value::INFO,file,line,res); free(res); } void warn(const std::string &file,size_t line,const char*fmt,...) { //通過(guò)傳入的參數(shù)構(gòu)造出一個(gè)日志消息對(duì)象,進(jìn)行格式化,最終落地 //1.判斷當(dāng)前的日志輸出等級(jí)是否達(dá)到輸出等級(jí) if(LogLevel::value::WARN<_limit_level) {return ;} //對(duì)fmt格式化字符串和不定參進(jìn)行字符串組織,得到日志消息字符串 va_list ap; va_start(ap,fmt); char* res; int ret=vasprintf(&res,fmt,ap); if(ret==-1) { std::cout<<"vasprintf failed!\n"; return; } va_end(ap); serialize(LogLevel::value::WARN,file,line,res); free(res); } void error(const std::string &file,size_t line,const char*fmt,...) { //通過(guò)傳入的參數(shù)構(gòu)造出一個(gè)日志消息對(duì)象,進(jìn)行格式化,最終落地 //1.判斷當(dāng)前的日志輸出等級(jí)是否達(dá)到輸出等級(jí) if(LogLevel::value::ERROR<_limit_level) {return ;} //對(duì)fmt格式化字符串和不定參進(jìn)行字符串組織,得到日志消息字符串 va_list ap; va_start(ap,fmt); char* res; int ret=vasprintf(&res,fmt,ap); if(ret==-1) { std::cout<<"vasprintf failed!\n"; return; } va_end(ap); serialize(LogLevel::value::ERROR,file,line,res); free(res); } void fatal(const std::string &file,size_t line,const char*fmt,...) { //通過(guò)傳入的參數(shù)構(gòu)造出一個(gè)日志消息對(duì)象,進(jìn)行格式化,最終落地 //1.判斷當(dāng)前的日志輸出等級(jí)是否達(dá)到輸出等級(jí) if(LogLevel::value::FATAL<_limit_level) {return ;} //對(duì)fmt格式化字符串和不定參進(jìn)行字符串組織,得到日志消息字符串 va_list ap; va_start(ap,fmt); char* res; int ret=vasprintf(&res,fmt,ap); if(ret==-1) { std::cout<<"vasprintf failed!\n"; return; } va_end(ap); serialize(LogLevel::value::FATAL,file,line,res); free(res); } protected: void serialize(LogLevel::value level,const std::string &file,size_t line,char*str) { //1.構(gòu)造LogMsg對(duì)象 LogMsg msg(level,line,file,_logger_name,str); //2.通過(guò)格式化工具對(duì)LogMsg進(jìn)行格式化,得到格式化后的日志字符串 std::string ss=_formatter->format(msg); //3.進(jìn)行日志落地 log(ss.c_str(),ss.size()); } //抽象接口完成實(shí)際的落地輸出--不同的日志器會(huì)有不同的落地方式 virtual void log(const char* data,size_t len)=0; protected: std::mutex _mutex; std::string _logger_name; Formatter::ptr _formatter; std::vector<LogSink::ptr> _sinks; std::atomic<LogLevel::value> _limit_level; }; class SyncLogger:public Logger{ public: SyncLogger( const std::string &logger_name, Formatter::ptr &formatter, std::vector<LogSink::ptr> &sinks, LogLevel::value &level): Logger(logger_name, formatter, sinks, level) {} void log(const char* data,size_t len) { //同步日志器,是將日志直接通過(guò)落地模塊句柄進(jìn)行日志落地 std::unique_lock<std::mutex> lock(_mutex); if(_sinks.empty()) return; for(auto &sink:_sinks) { sink->sink(data,len); } } }; } #endif
測(cè)試代碼:test.cpp
//logger.cpp同步日志器測(cè)試 #include"logger.hpp" int main() { std::string logger_name="synclogger"; wz_logs::LogLevel::value limit=wz_logs::LogLevel::value::WARN; wz_logs::Formatter::ptr fmt(new wz_logs::Formatter("[%d{%H:%M:%S}][%c][%f:%l][%p]%T%m%n")); wz_logs::LogSink::ptr stdout_lsp=wz_logs::SinkFactory::creat<wz_logs::StdoutSink>(); wz_logs::LogSink::ptr file_lsp=wz_logs::SinkFactory::creat<wz_logs::FileSink>("./logfile/test.log"); wz_logs::LogSink::ptr roll_lsp=wz_logs::SinkFactory::creat<wz_logs::RollBySizeSink>("./logfile/roll-",1024*1024); std::vector<wz_logs::LogSink::ptr> sinks={stdout_lsp,file_lsp,roll_lsp}; wz_logs::Logger::ptr logger(new wz_logs::SyncLogger(logger_name,fmt,sinks,limit)); logger->debug(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->info(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->warn(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->error(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->fatal(__FILE__,__LINE__,"%s","測(cè)試日志"); size_t cursize=0,count=0; while(cursize<1024*1024*10) { logger->fatal(__FILE__,__LINE__,"測(cè)試日志-%d",count++); cursize+=20; } return 0; }
本測(cè)試用例實(shí)現(xiàn)同時(shí)向三個(gè)落地方向進(jìn)行輸出,測(cè)試結(jié)果如下:
建造者設(shè)計(jì)思想的引入:
我們?cè)谑褂萌罩酒髂K進(jìn)行日志打印之前,要進(jìn)行日志消息模塊、格式化器模塊等的大量的零部件構(gòu)造,如下圖:
也就是說(shuō)用戶真正使用到我們的日志器模塊之前還要自己承擔(dān)構(gòu)造各個(gè)零件的任務(wù),顯然,這對(duì)于用戶來(lái)說(shuō),體驗(yàn)感并不好,因此我們使用建造者模式來(lái)建造日志器,而不要讓用戶直接建造,簡(jiǎn)化用戶使用的復(fù)雜度,實(shí)現(xiàn)思想如下:
1.抽象一個(gè)日志器建造者基類:設(shè)置日志器類性、將所有的日志器創(chuàng)建放在同一個(gè)建造者類中完成;
2.派生出具體的建造者類--局部日志器的建造者、全局日志器的建造者
建造者模式初步實(shí)現(xiàn):
這部分代碼也在logger.hpp中:
enum LoggerType{ LOGGER_SYNC, LOGGER_ASYNC }; //1.抽象一個(gè)日志器建造者基類:設(shè)置日志器類性、將所有的日志器創(chuàng)建放在同一個(gè)建造者類中完成; class LoggerBuilder { public: LoggerBuilder():_logger_type(LoggerType::LOGGER_SYNC), _limit_level(wz_logs::LogLevel::value::DEBUG) {} void buildLoggerType(LoggerType type){_logger_type=type;} void buildLoggerName(const std::string &name){_logger_name=name;} void buildLoggerLevel(wz_logs::LogLevel::value level){_limit_level=level;} void buildFormatter(const std::string &pattern) { _formatter=std::make_shared<wz_logs::Formatter>(pattern); } template <typename SinkType,typename...Args> void buildSink(Args &&...args) { wz_logs::LogSink::ptr psink=SinkFactory::creat<SinkType>(std::forward<Args>(args)...); _sinks.push_back(psink); } virtual Logger::ptr build()=0; protected: LoggerType _logger_type; std::string _logger_name; wz_logs::Formatter::ptr _formatter; wz_logs::LogLevel::value _limit_level; std::vector<wz_logs::LogSink::ptr> _sinks; }; //2.派生出具體的建造者類--局部日志器的建造者、全局日志器的建造者 class LocalLoggerBuilder:public LoggerBuilder{ public: Logger::ptr build() override { assert(!_logger_name.empty());//日志器名稱是使用日志器的唯一標(biāo)識(shí),即必須有 if(_formatter.get()==nullptr) { _formatter=std::make_shared<Formatter>(); } if(_sinks.empty()) { buildSink<StdoutSink>(); } if(_logger_type==LoggerType::LOGGER_ASYNC) { } return std::make_shared<SyncLogger>(_logger_name,_formatter,_sinks,_limit_level); } };
? ? ? ? 看得出來(lái)調(diào)用接口來(lái)實(shí)現(xiàn)零件構(gòu)造明顯比上面直接構(gòu)造方便很多,由于我們對(duì)各個(gè)模塊構(gòu)建的順序不會(huì)影響到最后的落地情況,因此這里不需要再生成指揮者類。
測(cè)試代碼:test.cpp
//logger.cpp建造者模式下同步日志器測(cè)試 #include"logger.hpp" int main() { std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::LocalLoggerBuilder()); builder->buildLoggerName("sync_logger"); builder->buildLoggerLevel(wz_logs::LogLevel::value::WARN); builder->buildFormatter("%m%n"); builder->buildLoggerType(wz_logs::LoggerType::LOGGER_SYNC); builder->buildSink<wz_logs::FileSink>("./logfile/test.log"); builder->buildSink<wz_logs::StdoutSink>(); wz_logs::Logger::ptr logger=builder->build(); logger->debug(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->info(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->warn(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->error(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->fatal(__FILE__,__LINE__,"%s","測(cè)試日志"); size_t cursize=0,count=0; while(cursize<1024*1024*10) { logger->fatal(__FILE__,__LINE__,"測(cè)試日志-%d",count++); cursize+=20; } return 0; }
測(cè)試結(jié)果:
異步日志器:
異步緩沖區(qū)類--概念:
? ? ? ? 之前完成的同步日志器是直接將日志消息進(jìn)行格式化然后寫入文件,接下來(lái)的異步日志器實(shí)現(xiàn)思想如下:
? ? ? ?有了之前的鋪墊,我們知道,異步日志器的實(shí)現(xiàn)是為了避免因?yàn)閷懭罩镜倪^(guò)程阻塞,導(dǎo)致業(yè)務(wù)線程在寫日志的時(shí)候影響效率,我們的實(shí)現(xiàn)思想就是不讓業(yè)務(wù)線程進(jìn)行日志的實(shí)際落地操作,而是將日志消息放到緩沖區(qū)(一塊指定的內(nèi)存),接下來(lái)有一個(gè)專門的異步線程,去針對(duì)緩沖區(qū)中的數(shù)據(jù)進(jìn)行處理(實(shí)際的落地操作),如下圖:
那么我們要實(shí)現(xiàn)一個(gè)異步日志器,首先要有一個(gè)線程安全的緩沖區(qū),其次要有一個(gè)處理消息實(shí)際落地的異步工作線程,線程安全是靠對(duì)緩沖區(qū)的讀寫加鎖實(shí)現(xiàn)的。
緩沖區(qū)設(shè)計(jì):
1.使用隊(duì)列,緩存日志消息,逐條處理
? ? ? ?考慮到效率問(wèn)題,這個(gè)隊(duì)列不能涉及到空間的頻繁申請(qǐng)和釋放,否則會(huì)降低效率,那就可以設(shè)計(jì)一個(gè)環(huán)形隊(duì)列(提前將空間申請(qǐng)好,然后對(duì)空間進(jìn)行循環(huán)利用),但是這種方式會(huì)遇到嚴(yán)重的鎖沖突問(wèn)題,生產(chǎn)者與生產(chǎn)者的互斥,生產(chǎn)者與消費(fèi)者的互斥【因?yàn)閷懭罩静僮髟趯?shí)際開發(fā)過(guò)程中,并不會(huì)分配太多資源,所以工作線程只需要一個(gè)日志器就行,一般不考慮消費(fèi)者與消費(fèi)者之間的沖突】,為解決這一問(wèn)題,我們采用雙緩沖區(qū)的方式,思想如下:
? ? ? ? 雙緩沖區(qū)是處理器將一個(gè)緩沖區(qū)中的任務(wù)全部處理完,然后交換兩個(gè)緩沖區(qū),重新對(duì)新的緩沖區(qū)中的任務(wù)進(jìn)行處理,雖然同時(shí)多線程輸入也會(huì)沖突,但是沖突并不會(huì)像每次只處理一條的時(shí)候頻繁(主要是減少了生產(chǎn)者和消費(fèi)者之間的鎖沖突),且不涉及到空間的頻繁申請(qǐng)和釋放所帶來(lái)的消耗,結(jié)構(gòu)圖如下:
接下來(lái)我們對(duì)單個(gè)緩沖區(qū)進(jìn)行進(jìn)一步設(shè)計(jì):
? ? ? ? 生產(chǎn)者消費(fèi)者緩沖區(qū)我們用共同的一個(gè)緩沖區(qū)類來(lái)同時(shí)實(shí)現(xiàn),這個(gè)緩沖區(qū)類構(gòu)造后可以直接存放格式化后的日志消息字符串(這樣既可以減少LogsMsg對(duì)象的頻繁構(gòu)造,又可以針對(duì)緩沖區(qū)的日志消息,一次性進(jìn)行IO操作,減少IO次數(shù),提高效率),因此我們?cè)O(shè)計(jì)的緩沖區(qū)類應(yīng)該管理的成員以及需要提供的接口如下:
成員變量:
1.一個(gè)存放字符串?dāng)?shù)據(jù)的緩沖區(qū)(使用管理 )
2.當(dāng)前的寫入數(shù)據(jù)位置的指針(指向可寫區(qū)域的起始位置,避免數(shù)據(jù)寫入覆蓋)
3.當(dāng)前的讀取數(shù)據(jù)位置的指針(指向可讀數(shù)據(jù)區(qū)域的起始位置,當(dāng)讀取指針與寫入指針指向位置相同表示數(shù)據(jù)讀取完了)
成員函數(shù):
1.向緩沖區(qū)寫入數(shù)據(jù)
2.獲取可讀、可寫數(shù)據(jù)起始地址的接口
3.獲取可讀數(shù)據(jù)長(zhǎng)度的接口
4.移動(dòng)讀寫位置的接口
5.初始化緩沖區(qū)的操作(將一個(gè)緩沖區(qū)所有數(shù)據(jù)處理完畢之后,讀寫位置初始化)
6.交換緩沖區(qū)的操作
異步緩沖區(qū)類--實(shí)現(xiàn):
buffer.hpp?
#ifndef __M_BUF_H__ #define __M_BUF_H__ #include<vector> #include<cassert> #include<iostream> namespace wz_logs { #define DEFAULT_BUFFER_SIZE (1*1024*1024) //默認(rèn)緩沖區(qū)大小1M #define THRESHOLD_BUFFER_SIZE (10*1024*1024)//緩沖區(qū)閾值 #define INCREMENT_BUFFER_SIZE (1*1024*1024)//線性增量大小 class Buffer { public: Buffer():_buffer(DEFAULT_BUFFER_SIZE),_writer(0),_reader(0) {} //將數(shù)據(jù)push進(jìn)緩沖區(qū) void push(const char* data,size_t len) { //1.判斷容量,這里提供兩種處理方式,應(yīng)用于不同的場(chǎng)景 //1'數(shù)量達(dá)到設(shè)定好的大小,就阻塞/返回false---應(yīng)用于實(shí)際場(chǎng)景,因?yàn)楝F(xiàn)實(shí)中資源不可能是無(wú)限使用的 // if(_buffer.writeAblesize()<len) return; //2'數(shù)量按照一定的規(guī)則來(lái)擴(kuò)容,不設(shè)上限---應(yīng)用于測(cè)試場(chǎng)景 if(writeAblesize()<len) { containerReset(len); } //2.將數(shù)據(jù)拷貝到緩沖區(qū)可寫入起始地址處 std::copy(data,data+len,&_buffer[_writer]); //3.可寫入地址向后偏移 writemov(len); } //讀位置向后偏移 void readmov(size_t len) { assert(len<=_buffer.size()); _reader+=len; } //返回可讀位置起始地址 const char* begin() { return &_buffer[_reader]; } //重置緩沖區(qū),初始化緩沖區(qū) void reset() { _reader=0; _writer=0; } //返回緩沖區(qū)可讀取數(shù)據(jù)長(zhǎng)度 size_t readAblesize() { return _writer-_reader; } //返回緩沖區(qū)可寫入數(shù)據(jù)長(zhǎng)度 size_t writeAblesize() { return _buffer.size()-_writer; } //交換讀寫緩沖區(qū) void swap(Buffer &buf) { _buffer.swap(buf._buffer); std::swap(_writer,buf._writer); std::swap(_reader,buf._reader); } //判斷緩沖區(qū)是否為空 bool empty() { return _writer==_reader; } private: //寫指針向后偏移 void writemov(size_t len) { assert((len+_writer)<=_buffer.size()); _writer+=len; } //擴(kuò)容 void containerReset(const size_t &len) { //為保證合理性,我們采用閾值的方式來(lái)劃分?jǐn)U容方式 //設(shè)定一個(gè)閾值,沒(méi)達(dá)到閾值之間,2倍擴(kuò)容,達(dá)到閾值后,線性擴(kuò)容 size_t newsize=0; if(_buffer.size()<THRESHOLD_BUFFER_SIZE) { newsize=_buffer.size()*2+len;//+len是因?yàn)閿U(kuò)容后可能容量依舊不夠,即newsize<len } else { newsize=_buffer.size()+INCREMENT_BUFFER_SIZE+len;//日志一般不會(huì)存在擴(kuò)容后空間仍然不夠的問(wèn)題,但是也要考慮到異常的處理 } _buffer.resize(newsize); } private: std::vector<char> _buffer; size_t _writer;//本質(zhì)是當(dāng)前可寫位置的下標(biāo) size_t _reader;//本質(zhì)是當(dāng)前可讀位置的下標(biāo) }; } #endif
測(cè)試代碼:test.cpp
//buffer.hpp的測(cè)試 //由于在此環(huán)境下,不好去查看內(nèi)存空間值的情況 //因此為了方便,我們和文件讀取寫入配合的操作來(lái)測(cè)試,具體思想如下: //讀取文件數(shù)據(jù),逐字節(jié)寫入緩沖區(qū),最終將緩沖區(qū)數(shù)據(jù)寫入文件,判斷生成的新文件與源文件是否一致 #include "buffer.hpp" #include <fstream> int main() { //ifstream:該數(shù)據(jù)類型表示輸入文件流,用于從文件讀取信息。 std::ifstream ifs("./logfile/test.log",std::ios::binary); if(ifs.is_open()==false) return -1; ifs.seekg(0,std::ios::end);//讀寫位置跳轉(zhuǎn)至文件末尾 size_t fsize=ifs.tellg();//獲取當(dāng)前讀寫位置相對(duì)于起始位置的偏移量 ifs.seekg(0,std::ios::beg);//重新跳轉(zhuǎn)至起始位置 std::string body; body.resize(fsize); ifs.read(&body[0],fsize); if(ifs.good()==false) { std::cout<<"read error\n"; return -1; } ifs.close(); std::cout<<fsize<<std::endl; wz_logs::Buffer buffer; for(int i=0; i<body.size();i++) { buffer.push(&body[i],1); } std::ofstream ofs("./logfile/tmp.log",std::ios::binary);//測(cè)試時(shí),一定要保證這個(gè)文件是存在的,否則運(yùn)行時(shí)找不到會(huì)報(bào)錯(cuò) size_t bsize=buffer.readAblesize(); for(int i=0;i<bsize;i++) { ofs.write(buffer.begin(),1); if(ofs.good()==false) {std::cout<<"write error\n";return -1;} buffer.readmov(1); } ofs.close(); return 0; }
測(cè)試結(jié)果:
補(bǔ)充知識(shí):MD5算法常常被用來(lái)驗(yàn)證網(wǎng)絡(luò)文件傳輸?shù)耐暾?,防止文件被人篡改。MD5 全稱是報(bào)文摘要算法(Message-Digest Algorithm 5),此算法對(duì)任意長(zhǎng)度的信息逐位進(jìn)行計(jì)算,產(chǎn)生一個(gè)二進(jìn)制長(zhǎng)度為128位(十六進(jìn)制長(zhǎng)度就是32位)的“指紋”(或“報(bào)文摘要”),不同的文件產(chǎn)生相同的報(bào)文摘要的可能性是非常非常之小的,常用來(lái)驗(yàn)證兩文件內(nèi)容是否完全一致。
異步工作器--概念:
設(shè)計(jì)思想:異步處理器+數(shù)據(jù)池(雙緩沖區(qū))
? ? ? ? 使用者將需要完成的任務(wù)添加到任務(wù)池中,由異步線程來(lái)完成任務(wù)的實(shí)際執(zhí)行操作。
由此異步工作器需要管理的成員變量及提供的操作就很清楚了,如下:
私有成員變量:
1.雙緩沖區(qū)(生產(chǎn)、消費(fèi))
2.互斥鎖(保證線程安全)
3.條件變量--生產(chǎn)+ 消費(fèi)(生產(chǎn)緩沖區(qū)沒(méi)有數(shù)據(jù),處理完消費(fèi)緩沖區(qū)數(shù)據(jù)后就休眠)
4.回調(diào)函數(shù)(針對(duì)緩沖區(qū)中數(shù)據(jù)的處理接口-外部傳入一個(gè)函數(shù),告訴異步工作器數(shù)據(jù)該如何處理)
提供的操作接口:
1.停止異步工作器
2.添加數(shù)據(jù)到緩沖區(qū)
私有操作接口:
創(chuàng)建線程,線程入口函數(shù)中,交換緩沖區(qū),對(duì)消費(fèi)緩沖區(qū)數(shù)據(jù)使用回調(diào)函數(shù)進(jìn)行處理,處理完后再次交換
異步工作器--實(shí)現(xiàn):?
looper.hpp
#ifndef __M_LOOPER_H__ #define __M_LOOPER_H__ #include"buffer.hpp" #include<thread> #include<mutex> #include<condition_variable> #include<atomic> #include<functional> #include<memory> namespace wz_logs { using Functor=std::function<void(Buffer &)>; enum class Looper_Type { ASYNC_SAFE,//安全狀態(tài),緩沖區(qū)滿了則阻塞,避免資源耗盡的風(fēng)險(xiǎn),用于實(shí)踐 ASYNC_UNSAFE//非安全,不考慮資源耗盡問(wèn)題,無(wú)限擴(kuò)容,常用于測(cè)試 }; class AsyncLooper { public: using ptr=std::shared_ptr<AsyncLooper>; AsyncLooper(const Functor &cb,Looper_Type loop_type=Looper_Type::ASYNC_SAFE) :_stop(false) ,_thread(std::thread(&AsyncLooper::threadEntry,this)) ,_callBack(cb) ,_looper_type(loop_type) {} ~AsyncLooper() { stop(); } void stop() { _stop=true;//將退出標(biāo)志設(shè)置為true _cond_con.notify_all();//喚醒所有的工作線程 } void push(const char* data,size_t len) { //1.無(wú)限擴(kuò)容是不安全的;2.固定大小生產(chǎn)緩沖區(qū)滿了就阻塞 std::unique_lock<std::mutex> lock(_mutex); //2.條件變量控制,若緩沖區(qū)剩余空間大小大于數(shù)據(jù)長(zhǎng)度,則可以添加數(shù)據(jù) if(_looper_type==Looper_Type::ASYNC_SAFE) _cond_pro.wait(lock,[&](){return _pro_buf.writeAblesize()>=len; }); //3.能夠走下來(lái)說(shuō)明條件滿足,可以向緩沖區(qū)添加數(shù)據(jù) _pro_buf.push(data,len); //4.喚醒消費(fèi)者對(duì)緩沖區(qū)進(jìn)行數(shù)據(jù)處理 _cond_con.notify_one(); } private: //線程入口函數(shù) //對(duì)消費(fèi)緩沖區(qū)中的數(shù)據(jù)進(jìn)行處理,處理完成后,初始化緩沖區(qū),交換緩沖區(qū) void threadEntry() { while(1) { { //1.判斷生產(chǎn)緩沖區(qū)有沒(méi)有數(shù)據(jù),有則交換,無(wú)則阻塞 //為互斥鎖設(shè)置一個(gè)生命周期,當(dāng)緩沖區(qū)交換完畢后就解鎖 std::unique_lock<std::mutex> lock(_mutex); if(_stop&&_pro_buf.empty()) break; //若當(dāng)前是退出前被喚醒,或者所有數(shù)據(jù)被喚醒,則返回真,繼續(xù)向下運(yùn)行,否則重新進(jìn)入休眠 _cond_con.wait(lock,[&](){return _stop||!_pro_buf.empty();});//這里是為了需要等退出標(biāo)志置為true且生產(chǎn)緩沖區(qū)數(shù)據(jù)已經(jīng)全被處理完了,才進(jìn)行阻塞 _con_buf.swap(_pro_buf); //2.交換完了,就喚醒生產(chǎn)者 if(_looper_type==Looper_Type::ASYNC_SAFE) _cond_pro.notify_all(); } //3.被喚醒后,對(duì)消費(fèi)緩沖區(qū)進(jìn)行數(shù)據(jù)處理 _callBack(_con_buf); //4.初始化消費(fèi)緩沖區(qū) _con_buf.reset(); } } Functor _callBack;//具體對(duì)緩沖區(qū)數(shù)據(jù)進(jìn)行處理的回調(diào)函數(shù),由異步工作器使用者傳入 private: Looper_Type _looper_type; std::atomic<bool> _stop;//工作器停止標(biāo)志 Buffer _pro_buf;//生產(chǎn)者緩沖區(qū) Buffer _con_buf;//消費(fèi)者緩沖區(qū) std::mutex _mutex; std::condition_variable _cond_pro; std::condition_variable _cond_con; std::thread _thread;//異步工作器對(duì)應(yīng)的工作線程 }; } #endif
測(cè)試代碼:test.cpp
//?。。。?!這個(gè)有問(wèn)題,有待測(cè)試?。。。。?/ //總會(huì)有如下報(bào)錯(cuò),應(yīng)該是線程異常退出的問(wèn)題// //terminate called without an active exception Aborted// //logger.cpp建造者模式下異步日志器測(cè)試 #include"logger.hpp" int main() { std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::LocalLoggerBuilder()); builder->buildLoggerName("async_logger"); builder->buildLoggerLevel(wz_logs::LogLevel::value::WARN); builder->buildFormatter("[%c]%m%n"); builder->buildLoggerType(wz_logs::LoggerType::LOGGER_ASYNC); //builder->buildEnableUnSafeAsync(); builder->buildSink<wz_logs::FileSink>("./logfile/async.log"); builder->buildSink<wz_logs::StdoutSink>(); wz_logs::Logger::ptr logger=builder->build(); logger->debug(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->info(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->warn(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->error(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->fatal(__FILE__,__LINE__,"%s","測(cè)試日志"); size_t cursize=0,count=0; while(cursize<1024*1024*10) { logger->fatal(__FILE__,__LINE__,"測(cè)試日志-%d",count++); cursize+=20; } return 0; }
測(cè)試等將異步日志器寫完,即完整的日志器模塊基本實(shí)現(xiàn)再來(lái)測(cè)試功能。
日志器模塊完善--異步日志器的實(shí)現(xiàn) :
logger.hpp
class AsyncLogger:public Logger{ public: AsyncLogger( const std::string &logger_name, Formatter::ptr &formatter, std::vector<LogSink::ptr> &sinks, LogLevel::value &level, Looper_Type looper_type): Logger(logger_name, formatter, sinks, level) , _looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::realLog,this,std::placeholders::_1),looper_type)){} //將數(shù)據(jù)寫入緩沖區(qū) void log(const char* data,size_t len) { _looper->push(data,len); } //設(shè)計(jì)一個(gè)實(shí)際落地函數(shù)(將緩沖區(qū)中的數(shù)據(jù)落地) void realLog(Buffer &buf) { if(_sinks.empty()) return; for(auto &sink:_sinks) { sink->sink(buf.begin(),buf.readAblesize()); } } private: AsyncLooper::ptr _looper;//要管理一個(gè)異步工作器 };
此時(shí),同步日志器和異步日志器我們就基本都實(shí)現(xiàn)了,接下來(lái)我們用之前測(cè)試同步日志器的代碼來(lái)對(duì)異步日志器的功能進(jìn)行測(cè)試。
?測(cè)試代碼:test.cpp
//?。。。?!這個(gè)有問(wèn)題,有待測(cè)試?。。。。?/ //總會(huì)有如下報(bào)錯(cuò),應(yīng)該是線程異常退出的問(wèn)題// //terminate called without an active exception Aborted// //logger.cpp建造者模式下異步日志器測(cè)試 #include"logger.hpp" int main() { std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::LocalLoggerBuilder()); builder->buildLoggerName("async_logger"); builder->buildLoggerLevel(wz_logs::LogLevel::value::WARN); builder->buildFormatter("[%c]%m%n"); builder->buildLoggerType(wz_logs::LoggerType::LOGGER_ASYNC); //builder->buildEnableUnSafeAsync(); builder->buildSink<wz_logs::FileSink>("./logfile/async.log"); builder->buildSink<wz_logs::StdoutSink>(); wz_logs::Logger::ptr logger=builder->build(); logger->debug(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->info(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->warn(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->error(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->fatal(__FILE__,__LINE__,"%s","測(cè)試日志"); size_t cursize=0,count=0; while(cursize<1024*1024*10) { logger->fatal(__FILE__,__LINE__,"測(cè)試日志-%d",count++); cursize+=20; } return 0; }
測(cè)試結(jié)果:
?可見,我們內(nèi)部的實(shí)現(xiàn)有一些問(wèn)題,測(cè)試結(jié)果如下,具體原因尚未排查出來(lái):
建造者模式進(jìn)一步實(shí)現(xiàn) :
logger.hpp
//注釋處多為完善增加的內(nèi)容 enum LoggerType{ LOGGER_SYNC, LOGGER_ASYNC }; class LoggerBuilder { public: LoggerBuilder():_logger_type(LoggerType::LOGGER_SYNC), _limit_level(wz_logs::LogLevel::value::DEBUG) {} void buildLoggerType(LoggerType type){_logger_type=type;} void buildLoggerName(const std::string &name){_logger_name=name;} void buildLoggerLevel(wz_logs::LogLevel::value level){_limit_level=level;} //新增接口--設(shè)置異步日志器的工作模式,一般極限測(cè)試性能時(shí),調(diào)用此接口即可設(shè)置日志器工作模式 void buildEnableUnSafeAsync() { _looper_type=Looper_Type::ASYNC_UNSAFE; } void buildFormatter(const std::string &pattern) { _formatter=std::make_shared<wz_logs::Formatter>(pattern); } template <typename SinkType,typename...Args> void buildSink(Args &&...args) { wz_logs::LogSink::ptr psink=SinkFactory::creat<SinkType>(std::forward<Args>(args)...); _sinks.push_back(psink); } virtual Logger::ptr build()=0; protected: //新增成員變量--異步工作器工作模式 LoggerType _logger_type;//針對(duì)異步日志器提供的 std::string _logger_name; wz_logs::Formatter::ptr _formatter; wz_logs::LogLevel::value _limit_level; std::vector<wz_logs::LogSink::ptr> _sinks; Looper_Type _looper_type; }; class LocalLoggerBuilder:public LoggerBuilder{ public: Logger::ptr build() override { assert(!_logger_name.empty());//日志器名稱是使用日志器的唯一標(biāo)識(shí),即必須有 if(_formatter.get()==nullptr) { _formatter=std::make_shared<Formatter>(); } if(_sinks.empty()) { buildSink<StdoutSink>(); } //完善構(gòu)建異步日志器智能指針對(duì)象 if(_logger_type==LoggerType::LOGGER_ASYNC) { //記得異步日志器還多一個(gè)參數(shù)【用來(lái)設(shè)置異步日志器的工作模式】 return std::make_shared<AsyncLogger>(_logger_name,_formatter,_sinks,_limit_level,_looper_type); } return std::make_shared<SyncLogger>(_logger_name,_formatter,_sinks,_limit_level); } };
日志器管理器模塊
概念:
作用:對(duì)所有創(chuàng)建的日志器統(tǒng)一進(jìn)行管理。
特性:以單例模式來(lái)實(shí)現(xiàn)日志器管理器,這樣我們就可以在程序的任意位置,獲取相同的單例對(duì)象,獲取其中的日志器進(jìn)行日志輸出。
擴(kuò)展:?jiǎn)卫芾砥鲃?chuàng)建時(shí),默認(rèn)先創(chuàng)建一個(gè)日志器(用于進(jìn)行標(biāo)準(zhǔn)輸出打印,方便用戶使用)
綜上,我們要實(shí)現(xiàn)的單例模式管理器應(yīng)包含以下內(nèi)容:
管理的成員:
1.默認(rèn)日志器
2.管理的日志器數(shù)據(jù)結(jié)構(gòu)
3.互斥鎖
提供的接口:
1.添加日志器管理
2.判斷是否管理了指定名稱的日志器
3.獲取指定名稱的日志器
4。獲取默認(rèn)日志器
實(shí)現(xiàn):
logger.hpp
//設(shè)置一個(gè)單例模式的日志器管理類 class LoggerManager{ public: static LoggerManager& getInstance() { //C++11后的新特性,針對(duì)靜態(tài)局部變量,編譯器實(shí)現(xiàn)了線程安全 //也就是說(shuō),在靜態(tài)局部變量沒(méi)有構(gòu)造完成之前,其他線程就會(huì)進(jìn)入阻塞 static LoggerManager eton; return eton; } void addLogger(Logger::ptr &logger) { if(hasLogger(logger->name())) return; std::unique_lock<std::mutex> lock(_mutex); _loggers.insert(std::make_pair(logger->name(),logger)); } bool hasLogger(const std::string &name) { std::unique_lock<std::mutex> lock(_mutex); auto it=_loggers.find(name); if(it==_loggers.end()) return false; return true; } Logger::ptr getLogger(const std::string &name) { std::unique_lock<std::mutex> lock(_mutex); auto it=_loggers.find(name); if(it==_loggers.end()) { return Logger::ptr(); } return it->second; } Logger::ptr rootLogger() { return _root_logger; } private: LoggerManager() { //構(gòu)造默認(rèn)日志器 std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::LocalLoggerBuilder()); builder->buildLoggerName("root"); _root_logger=builder->build(); _loggers.insert(std::make_pair("root",_root_logger)); } private: std::mutex _mutex; Logger::ptr _root_logger; std::unordered_map<std::string,Logger::ptr> _loggers; };
? ? ? ?實(shí)現(xiàn)之后,結(jié)合實(shí)際情況思考,我們發(fā)現(xiàn),當(dāng)我們用戶在創(chuàng)建了一個(gè)日志器后想要進(jìn)行全局的管理時(shí),就要把這個(gè)日志器添加到我們的單例對(duì)象中,然而我們發(fā)現(xiàn),用戶自己添加的過(guò)程是比較麻煩的,需要?jiǎng)佑肔oggerManager::getInstence().addLooger()才能完成添加,無(wú)疑增加了用戶的使用復(fù)雜度,這個(gè)時(shí)候我們就要繼續(xù)完善我們的建造者類,我們之前實(shí)現(xiàn)的LocalLoggerBuilder可以看做是本地建造者,哪里創(chuàng)建哪里使用,接下來(lái)我們就要再實(shí)現(xiàn)一個(gè)全局建造者,GlobalLoggerBuilder,這個(gè)類同樣繼承于LoggerBuilder,但其中在創(chuàng)建好日志器后,直接將日志器添加到管理器中,因此,以后用戶想要?jiǎng)?chuàng)建一個(gè)全局的日志器就只需要使用GlobalLoggerBuilder進(jìn)行構(gòu)建即可,構(gòu)建好的日志器天然就在管理器中。
全局建造者:GlobalLoggerBuilder的實(shí)現(xiàn):
logger.hpp
class GlobalLoggerBuilder:public LoggerBuilder{ public: Logger::ptr build() override { assert(!_logger_name.empty());//日志器名稱是使用日志器的唯一標(biāo)識(shí),即必須有 if(_formatter.get()==nullptr) { _formatter=std::make_shared<Formatter>(); } if(_sinks.empty()) { buildSink<StdoutSink>(); } Logger::ptr logger; if(_logger_type==LoggerType::LOGGER_ASYNC) { logger=std::make_shared<AsyncLogger>(_logger_name,_formatter,_sinks,_limit_level,_looper_type); } else{ logger=std::make_shared<SyncLogger>(_logger_name,_formatter,_sinks,_limit_level); } //構(gòu)建好的日志器直接在內(nèi)部添加到單例對(duì)象中 LoggerManager::getInstance().addLogger(logger); return logger; } };
接下來(lái)我們就可以對(duì)管理器模塊以及全局建造者模塊來(lái)進(jìn)行統(tǒng)一的測(cè)試:
測(cè)試代碼:test.cpp
//日志器管理類及全局建造者類測(cè)試 #include"logger.hpp" void test_log() { wz_logs::Logger::ptr logger=wz_logs::LoggerManager::getInstance().getLogger("sync_logger"); logger->debug(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->info(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->warn(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->error(__FILE__,__LINE__,"%s","測(cè)試日志"); logger->fatal(__FILE__,__LINE__,"%s","測(cè)試日志"); size_t cursize=0,count=0; while(cursize<1024*1024*10) { logger->fatal(__FILE__,__LINE__,"測(cè)試日志-%d",count++); cursize+=20; } } int main() { //創(chuàng)建管理全局建造者對(duì)象的智能指針 std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::GlobalLoggerBuilder()); builder->buildLoggerName("sync_logger"); builder->buildLoggerLevel(wz_logs::LogLevel::value::WARN); builder->buildFormatter("%m%n"); builder->buildLoggerType(wz_logs::LoggerType::LOGGER_SYNC); builder->buildSink<wz_logs::FileSink>("./logfile/test.log"); builder->buildSink<wz_logs::StdoutSink>(); builder->build(); test_log(); return 0; }
全局建造者創(chuàng)建的同步日志器落地方向?yàn)闃?biāo)準(zhǔn)輸出及指定文件,測(cè)試結(jié)果如下:
目錄文件生成正常,輸出等級(jí)判斷正常:
7.全局接口的設(shè)計(jì)
概念:
? ? ? ?上面在test.cpp中測(cè)試各個(gè)模塊的功能時(shí),大多都是從我們?cè)O(shè)計(jì)者的角度去測(cè)試的,對(duì)于用戶來(lái)講顯然是不合理的,比如開始用全局建造者構(gòu)建單例對(duì)象管理的日志器,用戶怎么知道你有單例模式,還有在調(diào)用不同等級(jí)的日志輸出方法時(shí),又怎么知道要傳__LINE__和__FILE__兩個(gè)宏,還有用戶在使用時(shí),一般只需要有一個(gè)封裝好的頭文件即可,手動(dòng)去包含一大堆頭文件也是不合理的,因此為了對(duì)日志系統(tǒng)接口的使用便捷性進(jìn)行優(yōu)化,我們需要提供全局接口&宏函數(shù)。
思想:
1.提供獲取指定日志器的全局接口(避免用戶自己操作單例對(duì)象)
2.使用宏函數(shù)對(duì)日志器的接口進(jìn)行代理(代理模式)
3.提供宏函數(shù),直接通過(guò)默認(rèn)日志器進(jìn)行標(biāo)準(zhǔn)輸出打印,不用獲取日志器了
實(shí)現(xiàn):?
wz_log.hpp
#ifndef __M_WZLOG_H__ #define __M_WZLOG_H__ #include"logger.hpp" namespace wz_logs{ // 1.提供獲取指定日志器的全局接口(避免用戶自己操作單例對(duì)象) Logger::ptr getLogger(const std::string &name) { return wz_logs::LoggerManager::getInstance().getLogger(name); } Logger::ptr rootLogger() { return wz_logs::LoggerManager::getInstance().rootLogger(); } // 2.使用宏函數(shù)對(duì)日志器的接口進(jìn)行代理(代理模式) #define debug(fmt,...) debug(__FILE__,__LINE__,fmt,##__VA_ARGS__) #define info(fmt,...) info(__FILE__,__LINE__,fmt,##__VA_ARGS__) #define warn(fmt,...) warn(__FILE__,__LINE__,fmt,##__VA_ARGS__) #define error(fmt,...) error(__FILE__,__LINE__,fmt,##__VA_ARGS__) #define fatal(fmt,...) fatal(__FILE__,__LINE__,fmt,##__VA_ARGS__) // 3.提供宏函數(shù),直接通過(guò)默認(rèn)日志器進(jìn)行標(biāo)準(zhǔn)輸出打印,不用獲取日志器了 #define DEBUG(fmt,...) wz_logs::rootLogger()->debug(fmt,##__VA_ARGS__) #define INFO(fmt,...) wz_logs::rootLogger()->info(fmt,##__VA_ARGS__) #define WARN(fmt,...) wz_logs::rootLogger()->warn(fmt,##__VA_ARGS__) #define ERROR(fmt,...) wz_logs::rootLogger()->error(fmt,##__VA_ARGS__) #define FATAL(fmt,...) wz_logs::rootLogger()->fatal(fmt,##__VA_ARGS__) } #endif
測(cè)試代碼:test.cpp
//全局接口測(cè)試 //提供封裝全局接口的頭文件之后,測(cè)試就可以只包含這個(gè)頭文件,符合用戶使用習(xí)慣 #include"wz_log.hpp" void test_log() { //1.調(diào)用封裝好的接口來(lái)獲取日志器 //wz_logs::Logger::ptr logger =wz_logs::getLogger("sync_logger"); wz_logs::Logger::ptr logger=wz_logs::LoggerManager::getInstance().getLogger("sync_logger"); logger->debug("%s","測(cè)試日志"); logger->info("%s","測(cè)試日志"); logger->warn("%s","測(cè)試日志"); logger->error("%s","測(cè)試日志"); logger->fatal("%s","測(cè)試日志"); size_t count=0; while(count<500000) { logger->fatal("測(cè)試日志-%d",count++); } //2.不用獲取日志器,直接用封裝好了默認(rèn)日志器的操作 // DEBUG("%s","測(cè)試日志"); // INFO("%s","測(cè)試日志"); // WARN("%s","測(cè)試日志"); // ERROR("%s","測(cè)試日志"); // FATAL("%s","測(cè)試日志"); // size_t count=0; // while(count<500000) // { // FATAL("測(cè)試日志-%d",count++); // } } int main() { std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::GlobalLoggerBuilder());//創(chuàng)建管理全局建造者對(duì)象的智能指針 builder->buildLoggerName("sync_logger"); builder->buildLoggerLevel(wz_logs::LogLevel::value::WARN); builder->buildFormatter("[%c][%f:%l]%m%n"); builder->buildLoggerType(wz_logs::LoggerType::LOGGER_SYNC); builder->buildSink<wz_logs::FileSink>("./logfile/test.log"); builder->buildSink<wz_logs::StdoutSink>(); builder->build(); test_log(); return 0; }
六、項(xiàng)目目錄結(jié)構(gòu)梳理
? ? ? ?考慮到我們項(xiàng)目的用戶層面實(shí)用性,我們要對(duì)整個(gè)項(xiàng)目的目錄結(jié)構(gòu)進(jìn)行梳理,最終用戶只需要拿到一個(gè)logs文件(里面包含實(shí)現(xiàn)各個(gè)模塊的頭文件),使用時(shí)只包含其中的一個(gè)提供全局接口的頭文件wz_log.hpp即可使用;同時(shí)還要有一個(gè)存放測(cè)試用例example文件(里面存放一些重要的測(cè)試用例,主要用于讓用戶快速熟悉這套系統(tǒng)的用法),另外還要有一個(gè)包含細(xì)碎知識(shí)點(diǎn)測(cè)試的practice文件(本項(xiàng)目中存放的是一些前置知識(shí)的測(cè)試代碼),最后再創(chuàng)建一個(gè)extend文件用來(lái)保存擴(kuò)展部分的代碼(本項(xiàng)目中擴(kuò)展的是落地方向,落地到以時(shí)間為劃分標(biāo)準(zhǔn)的滾動(dòng)文件)整理好的目錄結(jié)構(gòu)如下:
1.測(cè)試用例模塊功能檢測(cè):
代碼:./example/test.cpp
//全局接口測(cè)試 #include"../logs/wz_log.hpp" #include<unistd.h> void test_log(const std::string &name) { INFO("%s","測(cè)試開始"); wz_logs::Logger::ptr logger =wz_logs::getLogger("sync_logger"); logger->debug("%s","測(cè)試日志"); logger->info("%s","測(cè)試日志"); logger->warn("%s","測(cè)試日志"); logger->error("%s","測(cè)試日志"); logger->fatal("%s","測(cè)試日志"); INFO("%s","測(cè)試結(jié)束"); } int main() { std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::GlobalLoggerBuilder());//創(chuàng)建管理全局建造者對(duì)象的智能指針 builder->buildLoggerName("sync_logger"); builder->buildLoggerLevel(wz_logs::LogLevel::value::DEBUG); builder->buildFormatter("[%c][%f:%l]%m%n"); builder->buildLoggerType(wz_logs::LoggerType::LOGGER_SYNC); builder->buildSink<wz_logs::FileSink>("./logfile/test.log"); builder->buildSink<wz_logs::StdoutSink>(); builder->buildSink<wz_logs::RollBySizeSink>("./logfile/rool-sync-by-size",1024*1024); builder->build(); test_log("sync_logger"); return 0; }
測(cè)試結(jié)果:
2.拓展模塊功能檢測(cè):
實(shí)現(xiàn)+測(cè)試?代碼:./extend/test.cpp
#include"../logs/wz_log.hpp" #include<unistd.h> //擴(kuò)展模塊測(cè)試:模擬用戶自定義落地派生類模塊 //定義一個(gè)枚舉類,用戶使用時(shí)只需要傳入想要的枚舉變量即可,方便用戶使用 enum class TIMEGAP { GAP_SEC, GAP_MIN, GAP_HOUR, GAP_DAY }; //擴(kuò)展一個(gè)落地方向?yàn)橐詴r(shí)間為切換條件的滾動(dòng)文件的派生類 class RollByTimeSink:public wz_logs::LogSink { public: RollByTimeSink(const std::string &basename,TIMEGAP gap_type) :_basename(basename),_cur_gap(0) { switch(gap_type) { case TIMEGAP::GAP_SEC: _gap_size=1; break; case TIMEGAP::GAP_MIN: _gap_size=60; break; case TIMEGAP::GAP_HOUR: _gap_size=3600; break; case TIMEGAP::GAP_DAY: _gap_size=3600*24; break; } _cur_gap=_gap_size==1?wz_logs::util::Time::GetTime():wz_logs::util::Time::GetTime()%_gap_size; //1.創(chuàng)建文件及文件所在的目錄 std::string pathname=CreatNewFile(); wz_logs::util::File::CreatDirectory(wz_logs::util::File::Path(pathname)); //2.打開文件,以二進(jìn)制可追加的形式打開,符合文件寫入的條件 _ofs.open(pathname,std::ios::binary|std::ios::app); //文件打開才能進(jìn)行后續(xù)寫入,因此加個(gè)斷言 assert(_ofs.is_open()); } //判斷當(dāng)前文件的_cur_gap是否是當(dāng)前時(shí)間段,若不是,則要切換文件 void sink(const char *data,size_t len) { time_t cur=wz_logs::util::Time::GetTime(); if((cur%_gap_size)!=_cur_gap) { _ofs.close();//關(guān)閉原來(lái)打開的文件 std::string pathname=CreatNewFile(); _ofs.open(pathname,std::ios::binary|std::ios::app); assert(_ofs.is_open()); } _ofs.write(data,len); assert(_ofs.good()); } private: //進(jìn)行大小判斷,超過(guò)指定大小,創(chuàng)建新文件 std::string CreatNewFile() { //獲取系統(tǒng)時(shí)間,以時(shí)間來(lái)構(gòu)造文件拓展名 time_t t=wz_logs::util::Time::GetTime(); struct tm lt; localtime_r(&t,<); std::stringstream filename; filename<<_basename.c_str(); filename<<lt.tm_year+1900; filename<<lt.tm_mon+1; filename<<lt.tm_mday; filename<<lt.tm_hour; filename<<lt.tm_min; filename<<lt.tm_sec; filename<<".log"; return filename.str(); } private: //文件基礎(chǔ)名,一個(gè)系列的滾動(dòng)文件擁有共同的基礎(chǔ)名+各自的擴(kuò)展名 std::string _basename; std::ofstream _ofs; //用于記錄當(dāng)前的時(shí)間段 size_t _cur_gap; //用于記錄規(guī)定文件切換的時(shí)間段長(zhǎng)度,讓用戶自定義傳入 size_t _gap_size; }; //RollByTimeSink測(cè)試 int main() { std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::GlobalLoggerBuilder());//創(chuàng)建管理全局建造者對(duì)象的智能指針 builder->buildLoggerName("sync_logger"); builder->buildLoggerLevel(wz_logs::LogLevel::value::WARN); builder->buildFormatter("[%c][%f:%l]%m%n"); builder->buildLoggerType(wz_logs::LoggerType::LOGGER_SYNC); builder->buildSink<RollByTimeSink>("./logfile/rool-sync-by-size",TIMEGAP::GAP_SEC); wz_logs::Logger::ptr logger=builder->build(); time_t old=wz_logs::util::Time::GetTime(); while(wz_logs::util::Time::GetTime()<old+5) { logger->fatal("這是一條測(cè)試日志"); usleep(1000); } return 0; }
測(cè)試結(jié)果:
七、項(xiàng)目性能測(cè)試工具的設(shè)計(jì)?
? ? ? ?在我們完成一個(gè)項(xiàng)目之后,還有一步重要的步驟是不能忽略的,那就是進(jìn)行性能測(cè)試,項(xiàng)目作者需要設(shè)計(jì)出項(xiàng)目的性能測(cè)試工具,同時(shí)還要說(shuō)明當(dāng)前測(cè)試環(huán)境下的測(cè)試結(jié)果,也就是整個(gè)測(cè)試模塊需要包含測(cè)試三要素:測(cè)試環(huán)境、測(cè)試方法、測(cè)試結(jié)果,接下來(lái)我會(huì)基于我本人的環(huán)境依次對(duì)本項(xiàng)目進(jìn)行測(cè)試方法的提供以及測(cè)試結(jié)果的分析。
1.測(cè)試環(huán)境說(shuō)明
CPU:Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
RAM:2GB
ROM:50GB
OS:CentOS7.6
2.測(cè)試方法編寫
? ? ? ?我們要測(cè)試的內(nèi)容是分別在同步日志器和異步日志器下,單線程寫日志的性能和多線程寫日志的性能,即我們的測(cè)試工具要求可以控制寫日志的數(shù)量以及寫日志所用線程的總數(shù)量,實(shí)現(xiàn)思想如下:
1.封裝一個(gè)接口,傳入日志器名稱,日志數(shù)量、線程數(shù)量、單條日志消息的大小
在接口內(nèi):
創(chuàng)建一個(gè)(批)線程,分別負(fù)責(zé)一部分日志的輸出;
在輸出之前開始計(jì)時(shí),輸出完畢結(jié)束計(jì)時(shí),所耗時(shí)間=結(jié)束時(shí)間-起始時(shí)間;
每秒日志輸出量=日志數(shù)量/所耗時(shí)間;
每秒輸出大小=日志數(shù)量*單條日志大小/所耗時(shí)間;
需要注意:在測(cè)試異步輸入時(shí),我們啟動(dòng)非安全模式,純內(nèi)存寫入(不考慮實(shí)際落地時(shí)間)。
測(cè)試工具的實(shí)現(xiàn):bench.cc
#include"../logs/wz_log.hpp" #include<vector> #include<chrono> #include<thread> void bench(const std::string &loggername,size_t thr_num,size_t msg_num,size_t msg_len) { //獲取日志器 wz_logs::Logger::ptr logger=wz_logs::getLogger(loggername); if(logger.get()==nullptr) { return; } std::cout<<"測(cè)試日志:"<<msg_num<<"條,總大?。?<<(msg_num*msg_len)/1024<<"KB\n"; //2.組織指定長(zhǎng)度的日志消息 std::string msg(msg_len-1,'w');//-1是想要在每條日志消息最后填一個(gè)換行 //3.創(chuàng)建指定數(shù)量的線程 std::vector<std::thread> threads; std::vector<double> cost_arry(thr_num);//用來(lái)記錄每條線程處理日志消息的所用時(shí)間 size_t msg_per_thr=msg_num/thr_num;//每個(gè)線程需要輸出的日志數(shù)量=日志總量/線程總量,這里不準(zhǔn)確,存在不能整除,這里只為觀察現(xiàn)象,因此不作為重點(diǎn)處理, for(int i=0;i<thr_num;i++) { //emplace_back是vector提供的操作,功能是在vector已有的空間基礎(chǔ)上直接構(gòu)造并尾插 threads.emplace_back([&,i](){ //線程函數(shù)內(nèi)部開始計(jì)時(shí) auto start =std::chrono::high_resolution_clock::now(); //開始循環(huán)寫日志 for(int j=0;j<msg_per_thr;j++){ logger->fatal("%s",msg.c_str()); } //線程內(nèi)部結(jié)束計(jì)時(shí) auto end=std::chrono::high_resolution_clock::now(); std::chrono::duration<double> cost=end-start; cost_arry[i]=cost.count(); std::cout<<"線程"<<i<<":"<<"\t輸出數(shù)量"<<msg_per_thr<<"耗時(shí):"<<cost.count()<<"s\n"; }); } for(int i=0;i<thr_num;i++) { threads[i].join(); } //4.計(jì)算總耗時(shí):在多線程中,每個(gè)線程都會(huì)耗費(fèi)時(shí)間,但是線程是并發(fā)處理的,因此耗時(shí)最高的那個(gè)就是總時(shí)間 double max_cost=cost_arry[0]; for(int i=0;i<thr_num;i++) { max_cost=max_cost<cost_arry[i]?cost_arry[i]:max_cost; } size_t msg_per_sec=msg_num/max_cost;//每秒處理的日志消息數(shù)量 size_t size_per_sec=(msg_num*msg_len)/(max_cost*1024);//每秒處理的日志總大小 //打印測(cè)試結(jié)果 std::cout<<"每秒輸出日志數(shù)量:"<<msg_per_sec<<"條\n"; std::cout<<"每秒輸出日志大?。?<<size_per_sec<<"KB\n"; }
測(cè)試代碼:
void sync_bench() { std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::GlobalLoggerBuilder());//創(chuàng)建管理全局建造者對(duì)象的智能指針 builder->buildLoggerName("sync_logger"); builder->buildLoggerLevel(wz_logs::LogLevel::value::DEBUG); builder->buildFormatter("%m%n"); builder->buildLoggerType(wz_logs::LoggerType::LOGGER_SYNC); builder->buildSink<wz_logs::FileSink>("./logfile/test.log"); builder->build(); bench("sync_logger",1,1000000,100); //bench("sync_logger",3,1000000,100); } void async_bench() { std::unique_ptr<wz_logs::LoggerBuilder> builder(new wz_logs::GlobalLoggerBuilder());//創(chuàng)建管理全局建造者對(duì)象的智能指針 builder->buildLoggerName("sync_logger"); builder->buildLoggerLevel(wz_logs::LogLevel::value::DEBUG); builder->buildFormatter("%m%n"); builder->buildLoggerType(wz_logs::LoggerType::LOGGER_ASYNC); builder->buildSink<wz_logs::FileSink>("./logfile/test.log"); builder->build(); bench("async_logger",1,1000000,100); //bench("async_logger",3,1000000,100); } int main() { sync_bench(); //async_bench(); return 0; }
測(cè)試結(jié)果:
同步日志器下單線程測(cè)試結(jié)果:bench("sync_logger",1,1000000,100);
同步日志器下多線程測(cè)試結(jié)果:bench("sync_logger",3,1000000,100);
異步日志器下單線程測(cè)試結(jié)果:bench("async_logger",1,1000000,100);
異步日志器的實(shí)現(xiàn)有些問(wèn)題,還在排查中,后續(xù)會(huì)更新。。。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-702659.html
異步日志器下多線程測(cè)試結(jié)果:bench("async_logger",3,1000000,100);
異步日志器的實(shí)現(xiàn)有些問(wèn)題,還在排查中,后續(xù)會(huì)更新。。。
? ? 到此,一個(gè)支持多落地方向,且支持同步日志器和異步日志器的日志系統(tǒng)就完成了,異步日志器還存在一些問(wèn)題?,后續(xù)會(huì)排查并更正。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-702659.html
到了這里,關(guān)于【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!