国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】

這篇具有很好參考價(jià)值的文章主要介紹了【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

本項(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)圖如下:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

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)圖如下:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

三、項(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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

  • 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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

  • 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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

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àng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

懶漢模式實(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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

工廠模式

概念:

? ? ? ? 工廠模式是一種創(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àng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

工廠方法模式實(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àng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

抽象工廠模式實(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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

建造者模式

概念:

建造者模式是一種創(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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

代理模式

概念:

代理模式是指代理控制對(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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

四、項(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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

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è)試

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

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,&lt);
        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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

擴(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,&lt);
        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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

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í)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

建造者設(shè)計(jì)思想的引入:

我們?cè)谑褂萌罩酒髂K進(jìn)行日志打印之前,要進(jìn)行日志消息模塊、格式化器模塊等的大量的零部件構(gòu)造,如下圖:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式也就是說(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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

異步日志器:

異步緩沖區(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àng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

那么我們要實(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)圖如下:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

接下來(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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

補(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):

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

建造者模式進(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í)判斷正常:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(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;
}

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

六、項(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)如下:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

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,&lt);
        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é)果:

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

七、項(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);

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

同步日志器下多線程測(cè)試結(jié)果:bench("sync_logger",3,1000000,100);

【實(shí)例項(xiàng)目:基于多設(shè)計(jì)模式下的日志系統(tǒng)(同步&異步)】,設(shè)計(jì)模式

異步日志器下單線程測(cè)試結(jié)果:bench("async_logger",1,1000000,100);

異步日志器的實(shí)現(xiàn)有些問(wèn)題,還在排查中,后續(xù)會(huì)更新。。。

異步日志器下多線程測(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)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • C++項(xiàng)目實(shí)戰(zhàn)——基于多設(shè)計(jì)模式下的同步&異步日志系統(tǒng)-⑦-日志輸出格式化類設(shè)計(jì)

    C++項(xiàng)目實(shí)戰(zhàn)——基于多設(shè)計(jì)模式下的同步&異步日志系統(tǒng)-⑦-日志輸出格式化類設(shè)計(jì)

    ??作者簡(jiǎn)介: 花想云 ,在讀本科生一枚,C/C++領(lǐng)域新星創(chuàng)作者,新星計(jì)劃導(dǎo)師,阿里云專家博主,CSDN內(nèi)容合伙人…致力于 C/C++、Linux 學(xué)習(xí)。 ?? 專欄簡(jiǎn)介:本文收錄于 C++項(xiàng)目——基于多設(shè)計(jì)模式下的同步與異步日志系統(tǒng) ?? 相關(guān)專欄推薦: C語(yǔ)言初階系列 、 C語(yǔ)言進(jìn)階系列

    2024年02月07日
    瀏覽(20)
  • C++項(xiàng)目實(shí)戰(zhàn)——基于多設(shè)計(jì)模式下的同步&異步日志系統(tǒng)(總集篇)

    C++項(xiàng)目實(shí)戰(zhàn)——基于多設(shè)計(jì)模式下的同步&異步日志系統(tǒng)(總集篇)

    ??作者簡(jiǎn)介: 花想云 ,在讀本科生一枚,C/C++領(lǐng)域新星創(chuàng)作者,新星計(jì)劃導(dǎo)師,阿里云專家博主,CSDN內(nèi)容合伙人…致力于 C/C++、Linux 學(xué)習(xí)。 ?? 專欄簡(jiǎn)介:本文收錄于 C++項(xiàng)目——基于多設(shè)計(jì)模式下的同步與異步日志系統(tǒng) ?? 相關(guān)專欄推薦: C語(yǔ)言初階系列 、 C語(yǔ)言進(jìn)階系列

    2024年02月07日
    瀏覽(20)
  • C++項(xiàng)目實(shí)戰(zhàn)——基于多設(shè)計(jì)模式下的同步&異步日志系統(tǒng)-⑨-同步日志器類與日志器建造者類設(shè)計(jì)

    C++項(xiàng)目實(shí)戰(zhàn)——基于多設(shè)計(jì)模式下的同步&異步日志系統(tǒng)-⑨-同步日志器類與日志器建造者類設(shè)計(jì)

    ??作者簡(jiǎn)介: 花想云 ,在讀本科生一枚,C/C++領(lǐng)域新星創(chuàng)作者,新星計(jì)劃導(dǎo)師,阿里云專家博主,CSDN內(nèi)容合伙人…致力于 C/C++、Linux 學(xué)習(xí)。 ?? 專欄簡(jiǎn)介:本文收錄于 C++項(xiàng)目——基于多設(shè)計(jì)模式下的同步與異步日志系統(tǒng) ?? 相關(guān)專欄推薦: C語(yǔ)言初階系列 、 C語(yǔ)言進(jìn)階系列

    2024年02月08日
    瀏覽(16)
  • 【C++基于多設(shè)計(jì)模式下的同步&異步日志系統(tǒng)】

    【C++基于多設(shè)計(jì)模式下的同步&異步日志系統(tǒng)】

    本項(xiàng)?主要實(shí)現(xiàn)?個(gè)?志系統(tǒng), 其主要?持以下功能: 1???持多級(jí)別?志消息; 2???持同步?志和異步?志; 3???持可靠寫??志到控制臺(tái)、?件以及滾動(dòng)?件中; 4???持多線程程序并發(fā)寫?志; 5???持?jǐn)U展不同的?志落地?標(biāo). 1??CentOS 7.6(2核,內(nèi)存2GB,SSD云硬盤

    2024年02月08日
    瀏覽(20)
  • 設(shè)計(jì)模式之并發(fā)特定場(chǎng)景下的設(shè)計(jì)模式 Two-phase Termination(兩階段終止)模式

    在線程1中如何終止線程2? stop()?還是System.exit()?還是其他方式 1.使用stop()不可取 線程對(duì)象的stop()方法會(huì)直接殺死線程,假設(shè)此時(shí)使用了線程鎖,當(dāng)此時(shí)使用了stop()命令會(huì)導(dǎo)致線程鎖無(wú)法釋放,以至于程序出現(xiàn)嚴(yán)重的問(wèn)題,其中最常見的是死鎖。還可能導(dǎo)致資源泄露,因?yàn)?/p>

    2024年01月21日
    瀏覽(21)
  • 設(shè)計(jì)模式深度解析:AI大模型下的策略模式與模板方法模式對(duì)比解析

    設(shè)計(jì)模式深度解析:AI大模型下的策略模式與模板方法模式對(duì)比解析

    ??? 個(gè)人主頁(yè): danci_ ?? 系列專欄: 《設(shè)計(jì)模式》《MYSQL應(yīng)用》 ???? 制定明確可量化的目標(biāo),堅(jiān)持默默的做事。 策略模式與模板方法模式對(duì)比解析 ? ? ????在人工智能的世界里,設(shè)計(jì)模式不僅是構(gòu)建高效、可維護(hù)代碼的秘密武器,也是理解和掌握大模型內(nèi)在機(jī)制的鑰

    2024年04月08日
    瀏覽(25)
  • 模板設(shè)計(jì)模式-實(shí)例

    模板設(shè)計(jì)模式-實(shí)例

    ? ? 在軟件開發(fā)中,有時(shí)也會(huì)遇到類似的情況,某個(gè)方法的實(shí)現(xiàn)需要多個(gè)步驟(類似“請(qǐng)客”),其 中有些步驟是固定的(類似“點(diǎn)單”和“買單”),而有些步驟并不固定,存在可變性(類似“吃 東西”)。為了提高代碼的復(fù)用性和系統(tǒng)的靈活性,可以使用一種稱之為模

    2024年01月21日
    瀏覽(11)
  • 設(shè)計(jì)模式之解釋器模式詳解及實(shí)例

    1、解釋器設(shè)計(jì)模式概述: 解釋器模式(Interpreter Pattern)是一種設(shè)計(jì)模式,它主要用于描述如何構(gòu)建一個(gè)解釋器以解釋特定的語(yǔ)言或表達(dá)式。該模式定義了一個(gè)文法表示和解釋器的類結(jié)構(gòu),用于解釋符合該文法規(guī)則的語(yǔ)句。解釋器模式通常用于實(shí)現(xiàn)編程語(yǔ)言解釋器、自定義腳

    2024年02月12日
    瀏覽(90)
  • 一種純記憶模式下的智能駕駛行車設(shè)計(jì)方法

    一種純記憶模式下的智能駕駛行車設(shè)計(jì)方法

    摘要: 本文也重點(diǎn)闡述了記憶泊車的地圖構(gòu)建、資源需求和重定位的基礎(chǔ)算法。 與記憶泊車類似,記憶行車這一功能主要實(shí)現(xiàn)簡(jiǎn)單的面向服務(wù)的應(yīng)用服務(wù)。實(shí)現(xiàn)的典型應(yīng)用場(chǎng)景包括:上下班路線,接送兒童,商超采購(gòu)等。即,駕駛員通過(guò)在系統(tǒng)上設(shè)置想要到達(dá)的目的地,激

    2024年02月16日
    瀏覽(47)
  • Java 設(shè)計(jì)模式 - 單例模式 - 保證類只有一個(gè)實(shí)例

    單例模式是一種創(chuàng)建型設(shè)計(jì)模式,它確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問(wèn)點(diǎn)來(lái)獲取該實(shí)例。在某些情況下,我們需要確保一個(gè)類的實(shí)例在整個(gè)應(yīng)用程序中是唯一的,這時(shí)候單例模式就非常有用。在本篇博客中,我們將詳細(xì)探討單例模式的概念、實(shí)現(xiàn)方式以及如何在

    2024年02月15日
    瀏覽(20)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包