最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++簡單日志系統(tǒng)實現(xiàn)代碼示例

 更新時間:2025年11月19日 11:34:40   作者:hmcjn(小何同學(xué))  
日志系統(tǒng)是成熟軟件中的一個重要組成部分,其記錄軟件的使用和運行行為,方便事后進行故障分析、數(shù)據(jù)統(tǒng)計等,這篇文章主要介紹了C++簡單日志系統(tǒng)實現(xiàn)的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下

前言

生產(chǎn)環(huán)境的產(chǎn)品為了穩(wěn)定性和安全性是不支持開發(fā)人員去使用調(diào)試器去排查問題的;上線的客戶端或產(chǎn)品出現(xiàn)BUG無法復(fù)現(xiàn)并解決時;在分布式、多線程/多進程代碼中問題難以定位;開發(fā)人員就可以通過日志系統(tǒng)打印的日志來排查問題并及時解決。

同樣也可以幫助新人了解開發(fā)人員理解的運行邏輯。

Util.hpp

實現(xiàn)工具類:用于實現(xiàn)通用功能,供LogMsg(日志內(nèi)容封裝模塊)以及Sink(日志落地模塊)調(diào)用。

獲取當(dāng)前系統(tǒng)時間

class Date{
public:
    static time_t now(){
        return (time_t)time(nullptr);
    }
};

判斷文件是否存在

class File
{
public:
    static bool exists(const std::string &pathname){
        struct stat st;
        if (stat(pathname.c_str(), &st) < 0){
            return false;
        }
        return true;
    }
    ...
};

獲取文件的當(dāng)前路徑

class File
{
public:
    static std::string path(const std::string &pathname){
        //./abc/bdc/fgh
        // 查找  / 和 \ 中任意一個
        size_t pos = pathname.find_last_of("/\\");
        if (pos == std::string::npos)
            return ".";
        return pathname.substr(0, pos + 1);
    }
    ...
};

創(chuàng)建目錄

class File
{
public:
...
    static void createDirectory(const std::string &pathname){
    //  ./abbc/sss/op
    size_t pos = 0, index = 0;
    while (index < pathname.size())
    {
        pos = pathname.find_first_of("/\\", index);
        if (pos == std::string::npos){
            mkdir(pathname.c_str(), 0777);
        }
        std::string parentdir = pathname.substr(0, pos + 1);
        if (exists(parentdir) == true){
            index = pos + 1;
            continue;
        }
        mkdir(parentdir.c_str(), 0777);
        index = pos + 1;
    }
};

Level.hpp

日志等級類:供LogMsg(日志內(nèi)容封裝模塊)調(diào)用。

定義日志等級: INFO DEBUG WARN ERROR FATAL OFF。

class Loglevel{
public:
    //日志等級
    enum class value{
        UNKOWN = 0,
        INFO,
        DEBUG,
        WARN,
        ERROR,
        FATAL,
        OFF
    };
    ...
};

轉(zhuǎn)換字符串接口:將相應(yīng)的日志等級輸出成字符串。

class Loglevel{
public:
...
    static const char* toString(Loglevel::value level){
        switch (level){
            case Loglevel::value::INFO:return "INFO";
            case Loglevel::value::DEBUG:return "DEBUG";
            case Loglevel::value::WARN:return "WARN";
            case Loglevel::value::ERROR:return "ERROR";
            case Loglevel::value::FATAL:return "FATAL";
            case Loglevel::value::OFF:return "OFF";
            default: break;
        }
        return "UNKOWN";
    }
};

LogMsg.hpp

日志內(nèi)容封裝類:用于實現(xiàn)日志內(nèi)容的封裝,以供Format.hpp(日志格式化模塊)。

struct LogMsg
{
    time_t _ctime;          //日志產(chǎn)生的時間戳
    Loglevel::value _level; //日志等級
    std::string _file;      //源碼文件名       
    size_t _line;           //行號
    std::thread::id _tid;   //線程id
    std::string _logger;    //日志器名稱
    std::string _payload;   //有效載荷

    LogMsg(Loglevel::value level, size_t line, 
        const std::string file, const std::string logger,
        const std::string msg)
        : _ctime(Util::Date::now())
        , _level(level)
        , _file(file)
        , _line(line)
        , _tid(std::this_thread::get_id())
        ,_logger(logger)
        , _payload(msg)
    {
    }
};

Format.hpp

日志格式化類:主要負責(zé)日志的格式化,用于自定義格式,由格式化std::vector<FormatItem>來對格式進行存儲管理,需要使用到LogMsg(日志封裝模塊)。

其中給定了占位符,以及其將其替換的內(nèi)容:

  • %d:日期 ----子格式{%H:%M:%S}。

  • %t:線程id。

  • %T:縮進。

  • %p:日志等級。

  • %c:日志器名稱。

  • %f:文件名。

  • %l:行號。

  • %m:日志信息。

  • %n:換行。

FormatItem類主要負責(zé)?志消息?項的獲取及格式化。

其包含以下?類:

  • TimeFormatItem:從LogMsg中取出日志產(chǎn)生的系統(tǒng)時間。

  • FileFormatItem:從LogMsg中取出源碼文件名。

  • LineFormatItem:從LogMsg中取出源碼中調(diào)用日志的行號。

  • LevelFormatItem:從LogMsg中取出日志的等級。

  • ThreadFormatItem:從LogMsg中取出調(diào)用日志的線程ID

  • LoggerFormatItem:從LogMsg中取出日志器的名稱

  • MsgFormatItem:從LogMsg中取出有效載荷。

  • TableFormatItem:制表符

  • NlineFormatItem:換行符。

  • OtherFormatItem:非格式化的原始字符串

// 抽象 格式化子項
// 從日志消息中取出特定元素,追加到一塊空間
// 包括:時間 源文件 行號 日志等級 線程id 日志器名稱 有效載荷 制表符 換行符 其他
class FormatItem{
public:
    using ptr = std::shared_ptr<FormatItem>;
    virtual void format(std::ostream &out,const LogMsg &msg){};
};

class TimeFormatItem : public FormatItem{
public:
    TimeFormatItem(const std::string fmt = "%H:%M:%S") : _time_fmt(fmt) {}
    void format(std::ostream &out,const 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;
    }
    std::string _time_fmt;
};

class FileFormatItem : public FormatItem{
public:
    void format(std::ostream &out,const LogMsg &msg) override{
        out << msg._file;
    }
};

class LineFormatItem : public FormatItem{
public:
    void format(std::ostream &out,const LogMsg &msg) override{
        out << msg._line;
    }
};

class LevelFormatItem : public FormatItem{
public:
    void format(std::ostream &out,const LogMsg &msg) override{
        out << Loglevel::toString(msg._level);
    }
};

class ThreadFormatItem : public FormatItem{
public:
    void format(std::ostream &out,const LogMsg &msg) override{
        out << msg._tid;
    }
};

class LoggerFormatItem : public FormatItem{
public:
    void format(std::ostream &out,const LogMsg &msg) override{
        out << msg._logger;
    }
};

class MsgFormatItem : public FormatItem{
public:
    void format(std::ostream &out, const LogMsg &msg) override{
        out << msg._payload;
    }
};

class TableFormatItem : public FormatItem{
public:
    void format(std::ostream &out, const LogMsg &msg) override{
        out << "\t";
    }
};

class NlineFormatItem : public FormatItem{
public:
    void format(std::ostream &out,const LogMsg &msg) override{
        out << "\n";
    }
};

class OtherFormatItem : public FormatItem{
public:
    OtherFormatItem(const std::string &str) : _str(str) {}
    void format(std::ostream &out,const LogMsg &msg) override{
        out << _str;
    }
    std::string _str;
};

Formator類是對?志消息按格式化整理后輸出的執(zhí)行者。

std::string _pattern:用戶傳入的自定義格式。

class Formator{
public:
    using ptr = std::shared_ptr<Formator>;
    Formator(const std::string str = "[%d{%H:%M:%S}][%p][%t]%c %f %l  %T%m%n")
    :_pattern(str){
        assert(parseFormat());
    }
    //格式化后輸出到緩沖區(qū)
    void format(std::ostream &out, const LogMsg &msg){
        for (auto &fmt : _format_item)
            fmt->format(out, msg);
    }
    //格式化日志消息后返回內(nèi)容
    std::string format(const LogMsg &msg){
        std::stringstream ss;
        format(ss, msg);
        return ss.str();
    }
        ...
private:
    std::string _pattern;
    std::vector<FormatItem::ptr> _format_item;//各個格式化子項
};

bool parseFormat():解析傳入的自定義格式,并將格式及其對應(yīng)格式化子類指針填入std::vector<FormatItem>。

FormatItem::ptr createFormatItem():用于輔助parseFormat()填入格式對應(yīng)的指針。

class Formator{
    ...
private:
    //解析格式化規(guī)則字符串
    bool parseFormat(){
        // 1.是否為%,否則為原始字符串
        // 2.解析%
        // 3.%后是否有{}
        size_t pos = 0;
        std::vector<std::pair<std::string, std::string>> fmt_order;
        std::string key, val;
        while (pos < _pattern.size()){
            //普通字符串
            if (_pattern[pos] != '%'){
                val.push_back(_pattern[pos++]);
                continue;
            }
            // 是%%,存%字符
            if (pos + 1 < _pattern.size() && _pattern[pos + 1] == '%'){
                val.push_back(_pattern[pos + 1]);
                pos += 2;
                continue;
            }
            // val不為空,說明有普通字符串存在
            if (val.empty() == false){
                fmt_order.emplace_back("", val);
                val.clear();
            }

            pos += 1;
            if (pos == _pattern.size()){
                std::cout << "字符串格式錯誤!" << 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++]);
                // 沒有},說明格式錯誤
                if (pos == _pattern.size()){
                    std::cout << "字符串格式錯誤!" << std::endl;
                    return false;
                }
                pos += 1;
            }
            fmt_order.emplace_back(key, val);
            key.clear();
            val.clear();
        }

        for (auto &it : fmt_order)
            _format_item.push_back(createFormatItem(it.first, it.second));

        return true;
    }

    // 創(chuàng)建格式化子項
    FormatItem::ptr createFormatItem(std::string key, std::string val){
        if (key == "d")
            return std::make_shared<TimeFormatItem>(val);
        if (key == "t")
            return std::make_shared<ThreadFormatItem>();
        if (key == "T")
            return std::make_shared<TableFormatItem>();
        if (key == "n")
            return std::make_shared<NlineFormatItem>();
        if (key == "m")
            return std::make_shared<MsgFormatItem>();
        if (key == "f")
            return std::make_shared<FileFormatItem>();
        if (key == "p")
            return std::make_shared<LevelFormatItem>();
        if (key == "c")
            return std::make_shared<LoggerFormatItem>();
        if (key == "l")
            return std::make_shared<LineFormatItem>();
        if(key.empty())
            return std::make_shared<OtherFormatItem>(val);
        std::cout << "沒有對應(yīng)的格式化字符: %" << key << std::endl;
        abort();
        return nullptr;
    }
    ...
};

Sink.hpp

日志落地類:將日志輸出到指定的模塊,采用簡單工廠模式,這里給出三種落地模式,支持拓展。

用戶可以繼承基類LogSink來自定義實現(xiàn)一個落地方式。

class LogSink{
public:
    using ptr = std::shared_ptr<LogSink>;
    virtual void log(const char *data, size_t len) = 0;
    virtual ~LogSink() {}
};
提供一個log接口供子類實現(xiàn),用于日志落地。

標(biāo)準(zhǔn)輸出:StdoutSink,將日志輸出到標(biāo)準(zhǔn)輸出。

class StdoutSink : public LogSink{
public:
    // 將日志輸出到標(biāo)準(zhǔn)輸出
    void log(const char *data, size_t len) override{
        std::cout.write(data, len);
    }
};

指定文件:將日志輸出到指定文件。

  1. 查看文件是否存在,并創(chuàng)建文件。

  2. 獲取文件句柄。

class FileSink : public LogSink{
public:
    FileSink(const std::string &pathname) : _pathname(pathname){
        // 查看文件是否存在,并創(chuàng)建文件
        Util::File::createDirectory(Util::File::path(_pathname));
        // 獲取文件操作句柄
        _ofs.open(_pathname, std::ios::binary | std::ios::app);
        assert(_ofs.is_open());
    }
    // 將日志輸出到指定文件
    void log(const char *data, size_t len) override
    {
        _ofs.write(data, len);
        assert(_ofs.good());
    }

private:
    std::string _pathname; // 日志文件名
    std::ofstream _ofs;    // 文件句柄
};

滾動文件(按大小):將日志輸出到滾動文件。其思想可分為時間和大小,這里實現(xiàn)了按大小滾動。

  1. 按文件大小滾動,超過1G時會創(chuàng)建新文件,并寫入新文件。

  2. 按時間滾動,按約定的時間來滾動文件。

class RollByFileSink : public LogSink{
public:
    RollByFileSink(const std::string &basename, size_t max_size)
        : _basename(basename), _max_size(max_size), _cur_size(0),_name_cnt(0){
        createHelper();
    }

    // 將日志輸出到滾動文件
    void log(const char *data, size_t len) override
    {
        if (_cur_size >= _max_size)
        {
            _ofs.close();
            createHelper();
            _cur_size = 0;
            _name_cnt++;
        }
        _ofs.write(data, len);
        assert(_ofs.good());
        _cur_size += len;
    }
private:
    // 進行大小判斷,創(chuàng)建新文件
    std::string createNewFile(){
        struct tm lt;
        time_t now = Util::Date::now();
        localtime_r(&now, &lt);
        std::stringstream filename;
        filename << _basename;
        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 << _name_cnt;
        filename << ".log";
        return filename.str();
    }

    void createHelper()
    {
        // 獲取文件名
        std::string pathname = createNewFile();
        // 創(chuàng)建文件
        Util::File::createDirectory(Util::File::path(pathname));
        // 獲取文件操作句柄
        _ofs.open(pathname, std::ios::binary | std::ios::app);

        assert(_ofs.is_open());
    }

private:
    size_t _name_cnt;
    std::string _basename; // ./logs/base-(name)
    std::ofstream _ofs;
    size_t _max_size; // 記錄最大文件大小
    size_t _cur_size; // 記錄當(dāng)前已寫入的數(shù)據(jù)大小
};
傳入落地的方式即可--SinkType,如StdoutSink。
// 日志落地工廠
class SinkFactory{
public:
    template <typename SinkType, typename ...Args>
    static LogSink::ptr create(Args &&...args){
        return std::make_shared<SinkType>(std::forward<Args>(args)...);
    }
};

Buffer.hpp

日志緩沖區(qū)類:此類配合Looper中的AsyncLooper(異步日志器),來實現(xiàn)異步線程+雙緩沖區(qū)策略。旨在減少生產(chǎn)者與消費者的鎖沖突,避免了空間的頻繁申請與釋放,優(yōu)化日志的讀寫性能。

  • 其中日志器將消息寫到一個日志緩沖區(qū),日志緩存區(qū)再與任務(wù)緩沖區(qū)交互,由異步線程將任務(wù)緩沖區(qū)中的日志消息寫到磁盤。

  • _DEFAULT_SIZE:初始化緩沖區(qū)大小。

  • _THRESHOLD_SIZE:增長邊界,小于_THRESHOLD_SIZE且空間不足時按原有空間兩倍增長,大于則一次增加_INCREASE_SZIE

class Buffer{
    #define _DEFAULT_SIZE (1*1024*1024)
    #define _THRESHOLD_SIZE (8*1024*1024) 
    #define _INCREASE_SZIE (1*1024*1024)
public:
    Buffer():_buffer(_DEFAULT_SIZE),_write_idx(0),_read_idx(0){}

    //寫入數(shù)據(jù)
    void push(const char* data,size_t len){
        //空間不夠則擴容
        ensureEnough(len);

        std::copy(data,data + len, &_buffer[_write_idx]);
        moveWriter(len);
    }

    //返回可讀數(shù)據(jù)的起始地址
    const char* begin(){
        return &_buffer[_read_idx];
    }

    //移動可讀指針
    void moveReader(size_t len){
        assert(len <= readAbleSize());
        _read_idx += len;
    }

    //可讀空間
    size_t readAbleSize(){
        return (_write_idx - _read_idx);
    }

    //可寫空間
    size_t writeAbleSize(){
        //只為固定大小緩沖區(qū)提供的接口
        return (_buffer.size() - _write_idx);
    }

    //交換緩沖區(qū)
    void swap(Buffer &buffer){
        _buffer.swap(buffer._buffer);
        std::swap(_write_idx,buffer._write_idx);
        std::swap(_read_idx,buffer._read_idx);
    }

    //重置緩沖區(qū)
    void reset(){
        _write_idx = 0;
        _read_idx = 0;
    }

    //緩沖區(qū)是否為空
    bool empty(){
        return (_write_idx == _read_idx);
    }
private:
    //判斷緩沖區(qū)大小是否足夠
    void ensureEnough(size_t len){
        //無需擴容
        if(len < writeAbleSize()) return;
        
        //空間不足
        size_t newsize = 0;
        if(_buffer.size() >= _THRESHOLD_SIZE){
            newsize =  _buffer.size() + _INCREASE_SZIE + len;
        }
        else{
            newsize = _buffer.size() * 2 + len;
        }
        _buffer.resize(newsize);  
    }
    //移動可寫指針
    void moveWriter(size_t len){
        assert(_write_idx + len < _buffer.size());
        _write_idx += len;            
    }
private:
    std::vector<char> _buffer;
    size_t _write_idx;          //寫指針
    size_t _read_idx;           //讀指針
};

Looper.hpp

異步工作器:提供停止工作器,以及寫入緩沖區(qū)兩個接口,內(nèi)置兩個緩沖器,內(nèi)含異步線程入口函數(shù)。內(nèi)部提供一個回調(diào)函數(shù)指針,用于異步工作線程回調(diào)使用。

提供兩種緩存沖區(qū)策略:

  • ASYNC_SAFE:安全策略,當(dāng)緩沖區(qū)滿時阻塞寫入緩沖區(qū),防止資源耗盡。

  • ASYNC_UNSAFE:緩沖區(qū)無限擴容策略。

using Functor = std::function<void(Buffer &)>;
enum class AsyncType
{
    ASYNC_SAFE,     //安全狀態(tài),緩沖區(qū)滿就阻塞,防止資源耗盡
    ASYNC_UNSAFE    //緩沖區(qū)無限擴容
};

// 異步日志器
class AsyncLooper{
public:
    using ptr = std::shared_ptr<AsyncLooper>;

public:
    AsyncLooper(const Functor& cb,AsyncType runtype):_running(true),_callback(cb),
    _thread(std::thread(&AsyncLooper::threadEnter,this)),_runtype(AsyncType::ASYNC_SAFE)
    { _runtype = runtype;}

    ~AsyncLooper(){ stop(); }

    void stop()
    { 
        _running = false;
        //喚醒所以線程
        _cond_con.notify_all();
        _thread.join();
    }

    void push(const char *data, size_t len)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        //寫入長度小于緩沖區(qū)可寫長度才可寫,否則阻塞
        if(_runtype == AsyncType::ASYNC_SAFE)
            _cond_pro.wait(lock, [&](){ return len <= _pro_buffer.writeAbleSize();});

        _pro_buffer.push(data,len);

        //喚醒消費者 處理數(shù)據(jù)
        _cond_con.notify_one();
    }

private:
    //線程入口函數(shù)
    void threadEnter()
    {
        while(1)
        {
            //給互斥鎖設(shè)定作用域
            {
                std::unique_lock<std::mutex> lock(_mutex);
                //已設(shè)置退出狀態(tài)并且緩沖區(qū)為空,則退出
                if(!_running && _pro_buffer.empty())break;

                //程序結(jié)束前 或 生產(chǎn)者緩沖區(qū)不為空,進行數(shù)據(jù)寫入
                _cond_con.wait(lock, [&](){ return !_running || !_pro_buffer.empty();});

                _con_buffer.swap(_pro_buffer);
                //喚醒生產(chǎn)者
                if(_runtype == AsyncType::ASYNC_SAFE)
                    _cond_pro.notify_all();
            }
            //回調(diào)處理
            _callback(_con_buffer);
            //初始化消費緩沖區(qū)
            _con_buffer.reset();
        }
    }
private:
    // 回調(diào)函數(shù) 異步工作器使用者傳入
    Functor _callback;

private:
    AsyncType _runtype;
    bool _running;
    std::mutex _mutex;
    //生產(chǎn)緩沖區(qū)
    Buffer _pro_buffer;
    //消費緩沖區(qū)
    Buffer _con_buffer;
    std::condition_variable _cond_pro;
    std::condition_variable _cond_con;
    std::thread _thread;
};

Logger.hpp

日志類:使用建造者模式,來簡化使用難度。這個類主要用來與前端交互,需要打印日志時,只需創(chuàng)建Logger對象,并調(diào)用debug、info、warn等方法輸出日志即可。支持解析可變參數(shù)列表和輸出格式,可像printf一樣輸出日志。

  • 這里提供兩種日志的輸出方式:同步輸入日志以及異步輸出日志。

  • 同步輸出:同步輸出日志信息。

  • 異步輸出:將日志寫入到緩沖區(qū)中,由異步工作線程來完成日志輸出。

因此,設(shè)計基類Logger,由子類SyncLogger(同步輸出)以及AsyncLogger(異步輸出)來繼承。設(shè)置log接口由子類重寫即可。

支持傳入多個日志落地模塊,實現(xiàn)多種落地方式共同執(zhí)行。

class Logger{
public:
    using ptr = std::shared_ptr<Logger>;
    Logger(std::string logger_name, Loglevel::value level,
           Formator::ptr &formator, std::vector<LogSink::ptr> &sinks)
        : _logger_name(logger_name), _limit_level(level), _formator(formator), _sinks(sinks.begin(), sinks.end())
    {
    }
    // 完成對不同等級日志的構(gòu)造
    void debug(const std::string &file, size_t line, const std::string &fmt, ...){
        // 1.判斷日志輸出等級
        if (Loglevel::value::DEBUG < _limit_level)
            return;

        // 2.對不定參數(shù)解包
        va_list ap;
        va_start(ap, fmt);
        char *res;
        int ret = vasprintf(&res, fmt.c_str(), 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 std::string &fmt, ...){
        ...
    }

    void warn(const std::string &file, size_t line, const std::string &fmt, ...){
        ...
    }

    void error(const std::string &file, size_t line, const std::string &fmt, ...){
        ...
    }

    void fatal(const std::string &file, size_t line, const std::string &fmt, ...){
        ...
    }
    const std::string& name()
    {
        return _logger_name;
    }
protected:
    void serialize(Loglevel::value level, const std::string &file, size_t line, const char *str)
    {
        // 3.構(gòu)造LogMsg
        LogMsg logmsg(level, line, file, _logger_name, str);

        // 4.調(diào)用格式化工具格式化LogMsg
        std::stringstream ss;
        _formator->format(ss, logmsg);

        // 5.調(diào)用log輸出
        log(ss.str().c_str(), ss.str().size());
    }
    //由子類實現(xiàn)輸出邏輯
    virtual void log(const char *data, size_t len) = 0;

protected:
    std::mutex _mutex;                         // 互斥鎖
    std::string _logger_name;                  // 日志器名稱
    std::atomic<Loglevel::value> _limit_level; // 限制輸出的等級
    Formator::ptr _formator;                   // 格式化
    std::vector<LogSink::ptr> _sinks;          // 落地方式
};

同步日志器:

class SyncLogger : public Logger{
public:
    SyncLogger(std::string logger_name, Loglevel::value level,
               Formator::ptr &formator, std::vector<LogSink::ptr> &sinks)
        : Logger(logger_name, level, formator, sinks)
    {
    }

protected:
    void log(const char *data, size_t len) override{
        std::unique_lock<std::mutex> lock(_mutex);
        if (_sinks.empty())
            return;
        for (auto &sink : _sinks)
            sink->log(data, len);
    }
};

異步日志器:

class AsyncLogger : public Logger{
public:
    AsyncLogger(std::string logger_name, Loglevel::value level,
                Formator::ptr &formator, std::vector<LogSink::ptr> &sinks,
                AsyncType runtype)
        : Logger(logger_name, level, formator, sinks)
        , _looper(std::make_shared<AsyncLooper>(
            std::bind(&AsyncLogger::realLog, this, std::placeholders::_1)
            , runtype)
        ) //構(gòu)造時,傳入回調(diào)函數(shù)構(gòu)造異步日志器
    {
    }

    void log(const char *data, size_t len) override{
        _looper->push(data, len);
    }

    void realLog(Buffer &buf){
        if (_sinks.empty())
            return;
        for (auto &sink : _sinks)
            sink->log(buf.begin(), buf.readAbleSize());
    }

protected:
    AsyncLooper::ptr _looper; //異步日志器
};

日志器建造者模式設(shè)計:

由于Logger是對多個模塊的整合,想要創(chuàng)建日志器需要多個參數(shù),使用起來相對麻煩,因此設(shè)計建造者模式來降低使用難度。

日志建造者基類:

//日志器建造者
class LoggerBuilder
{
public:
    LoggerBuilder()
        : _logger_type(LoggerType::LOGGER_SYNC)
        , _limit_level(Loglevel::value::DEBUG)
        , _runtype(AsyncType::ASYNC_SAFE)
    {}
    // 建造各種變量的接口
    void buildUnSafeRuntype() { _runtype = AsyncType::ASYNC_UNSAFE; }

    void buildLoggerType(LoggerType type) { _logger_type = type; }

    void buildLoggerName(const std::string &name) { _logger_name = name; }

    void buildLoggerlevel(Loglevel::value level) { _limit_level = level; }

    void buildFormator(const std::string &pattern){
        _formator = std::make_shared<Formator>(pattern);
    }

    template <class SinkType, class... Args>
    void buildSink(Args &&...args)
    {
        LogSink::ptr sinkptr = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
        _sinks.push_back(sinkptr);
    }

    // 總建造函數(shù)由子類繼承重寫
    virtual Logger::ptr build() = 0;
protected:
    AsyncType _runtype;
    LoggerType _logger_type;
    std::string _logger_name;
    Loglevel::value _limit_level;
    Formator::ptr _formator;
    std::vector<LogSink::ptr> _sinks;
};

局部建造者:

// 局部建造者
class LoaclLoggerBuilder : public LoggerBuilder{
public:
    // 重寫父類建造函數(shù)
    Logger::ptr build() override{
        assert(!_logger_name.empty());
        if (_formator.get() == nullptr)
        {
            _formator = std::make_shared<Formator>();
        }
        if (_sinks.empty())
        {
            buildSink<StdoutSink>();
        }
        if (_logger_type == LoggerType::LOGGER_ASYNC)
        {
            return std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formator, _sinks, _runtype);
        }
        return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formator, _sinks);
    }
};

局部日志器的使用會受到作用域的限制,我們希望在全局各個地方都可以調(diào)用日志器進行輸出,因此設(shè)計了一個日志器管理的單例類,這樣我們就可以在任何地方調(diào)用這個管理器來獲取單例對象,來對日志進行打印。

我們基于日志類,繼承出一個全局日志器建造者類,建造出來的全局日志器直接添加到單例日志管理器中。

日志器管理器:

//日志器管理器  --默認日志器使用局部建造者創(chuàng)建并由管理器管理
class LoggerManager
{
public:
    static LoggerManager& getInstance(){
        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){
        if(_loggers.count(name))return true;
        return false;
    }

    Logger::ptr getLogger(const std::string& name){
        auto it = _loggers.find(name);
        if(it ==_loggers.end())return Logger::ptr();
        return it->second;
    }

    Logger::ptr rootLogger(){
        return _root_logger;
    }
private:
    LoggerManager(){
        std::unique_ptr<LoggerBuilder> _builder(new LoaclLoggerBuilder());
        _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;
};

全局建造者:

// 全局建造者
class GlobeLoggerBuilder : public LoggerBuilder{
public:
    Logger::ptr build() override
    {
        assert(!_logger_name.empty());
        if (_formator.get() == nullptr){
            _formator = std::make_shared<Formator>();
        }
        if (_sinks.empty()){
            buildSink<StdoutSink>();
        }
        Logger::ptr logger;
        if (_logger_type == LoggerType::LOGGER_ASYNC){
            logger = std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formator, _sinks, _runtype);
        }
        else{
            logger = std::make_shared<SyncLogger>(_logger_name, _limit_level, _formator, _sinks);
        }
        MyLogs::LoggerManager::getInstance().addLogger(logger);
        return logger;
    }
};

MyLog.hpp

這里提供全局日志器的獲取接口。

使用代理模式通過全局函數(shù)和宏函數(shù)來代理Logger中的debug、info、warn、fatal等接口,以便控制輸出日志的文件名和行號,簡化用戶操作。

namespace MyLogs
{
    //提供獲取指定日志器的全局接口
    Logger::ptr getLogger(const std::string &name)
    {
        return LoggerManager::getInstance().getLogger(name);
    }
    Logger::ptr rootLogger()
    {
        return LoggerManager::getInstance().rootLogger();
    }

    //使用宏對日志器接口進行代理    
    #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__)

    //提供宏,直接通過默認日志器進行輸出
    #define DEBUG(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
    #define INFO(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
    #define WARN(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
    #define ERROR(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
    #define FATAL(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
}

Bench.cc

測試環(huán)境:

  • CPU:AMD Ryzen 7 7700 8-Core Processor 5.35 GHz

  • RAM:32GB DDR5 6200

  • ROM:1T SSD PCIe3.0

  • OS:ubuntu-20.04.6TLS虛擬機

#include "../logs/MyLog.hpp"
#include <vector>
#include <thread>
#include <chrono>
using std::cout;
using std::endl;

void bench(const std::string &logger_name, size_t thr_cnt, size_t msg_cnt, size_t msg_len)
{
    //1.獲取日志器
    MyLogs::Logger::ptr logger = MyLogs::getLogger(logger_name);
    if(logger.get() == nullptr)return ;

    cout<< "測試日志: "<<msg_cnt<<"條," << "總大小: "<< (msg_cnt * msg_len) / 1024 << "KB" <<endl;
    //2.指定長度的日志
    std::string msg(msg_len-1 ,'A');
    //3.創(chuàng)建指定數(shù)量的線程
    std::vector<std::thread> threads;
    std::vector<double> threads_cost(thr_cnt,0);
    //每個線程需要輸出的日志數(shù)量
    size_t msg_per_thr = msg_cnt / thr_cnt;
    for(int i = 0; i < thr_cnt; i++)
    {
        threads.emplace_back([&,i](){
            //4.線程函數(shù)內(nèi)計時
            auto start = std::chrono::high_resolution_clock::now();
            //5.寫日志
            for(int j = 0; j < msg_per_thr; j++)
            {
                logger->fatal("%s",msg.c_str());

            }
            //6.結(jié)束計時
            auto end = std::chrono::high_resolution_clock::now();
            std::chrono::duration<double> cost = end - start;
            threads_cost[i] = cost.count();
            cout << "線程" << i << "\t輸出日志數(shù)量: " << msg_per_thr <<", 耗時: " << cost.count() << "s" <<endl;
        });
    }
    
    for(int i = 0; i < thr_cnt; i++)
        threads[i].join();

    //7.計算總耗時(多線程并發(fā),以線程最長耗時,為總耗時)
    double max_cost = threads_cost[0];
    for(auto thr_cost: threads_cost)
        max_cost = max_cost < thr_cost ? thr_cost : max_cost;
    size_t msg_per_sec = msg_cnt / max_cost;
    size_t size_per_sec = (msg_cnt * msg_len) / (max_cost * 1024);

    //8.打印消息
    cout << "\t總耗時: " << max_cost <<"s"<<endl;
    cout << "\t每秒輸出日志數(shù)量: " << msg_per_sec << "條" << endl;
    cout << "\t每秒輸出日志大小: " << size_per_sec << "KB "<<endl;
}

void syncLog()
{
    std::unique_ptr<MyLogs::LoggerBuilder> builder(new MyLogs::GlobeLoggerBuilder());
    builder->buildLoggerName("SyncLogger");
    builder->buildFormator("%m%n");
    builder->buildLoggerType(MyLogs::LoggerType::LOGGER_SYNC);
    builder->buildSink<MyLogs::FileSink>("./logfile/sync.log");
    builder->build();
    bench("SyncLogger",3,1000000,100);
}

void asyncLog()
{
    std::unique_ptr<MyLogs::LoggerBuilder> builder(new MyLogs::GlobeLoggerBuilder());
    builder->buildLoggerName("AsyncLogger");
    builder->buildFormator("%m%n");
    builder->buildLoggerType(MyLogs::LoggerType::LOGGER_ASYNC);
    builder->buildUnSafeRuntype();
    builder->buildSink<MyLogs::FileSink>("./logfile/async.log");
    builder->build();
    bench("AsyncLogger",3,1000000,100);
}

int main()
{
    syncLog();
    asyncLog();
    return 0;
}

測試結(jié)果:

同步日志:

測試日志: 1000000條,總大小: 97656KB

線程1 輸出日志數(shù)量: 333333, 耗時: 0.842788s

線程0 輸出日志數(shù)量: 333333, 耗時: 0.862125s

線程2 輸出日志數(shù)量: 333333, 耗時: 0.874029s

總耗時: 0.874029s

每秒輸出日志數(shù)量: 1144126條

每秒輸出日志大小: 111731KB

異步日志:

測試日志: 1000000條,總大小: 97656KB

線程0 輸出日志數(shù)量: 333333, 耗時: 0.246758s

線程1 輸出日志數(shù)量: 333333, 耗時: 0.247231s

線程2 輸出日志數(shù)量: 333333, 耗時: 0.250321s

總耗時: 0.250321s

每秒輸出日志數(shù)量: 3994875條

每秒輸出日志大小: 390124KB

總結(jié)

到此這篇關(guān)于C++簡單日志系統(tǒng)實現(xiàn)的文章就介紹到這了,更多相關(guān)C++日志系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++ 中的異常拋出和捕獲方式

    C++ 中的異常拋出和捕獲方式

    這篇文章主要介紹了C++ 中的異常拋出和捕獲方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • Qt掃盲篇之QRegularExpression正則匹配總結(jié)

    Qt掃盲篇之QRegularExpression正則匹配總結(jié)

    QRegularExpression是Qt5.0引進的,修復(fù)了很多bug,提高了效率,使用時建議使用QRegularExpression,下面這篇文章主要給大家介紹了關(guān)于Qt掃盲篇之QRegularExpression正則匹配的相關(guān)資料,需要的朋友可以參考下
    2023-03-03
  • 深入C/C++浮點數(shù)在內(nèi)存中的存儲方式詳解

    深入C/C++浮點數(shù)在內(nèi)存中的存儲方式詳解

    本篇文章是對C/C++浮點數(shù)在內(nèi)存中的存儲方式進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • OpenCV利用背景建模檢測運動物體

    OpenCV利用背景建模檢測運動物體

    這篇文章主要為大家詳細介紹了OpenCV利用背景建模檢測運動物體,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • C++教程之變量的作用域與生命周期詳解

    C++教程之變量的作用域與生命周期詳解

    在C++編程中,變量的作用域和生命周期是非常重要的概念。了解這些概念可以幫助開發(fā)人員編寫更好的代碼并避免錯誤。在本文中,我們將探討C++中變量的作用域和生命周期,以及如何正確地使用它們
    2023-04-04
  • 求解旋轉(zhuǎn)數(shù)組的最小數(shù)字

    求解旋轉(zhuǎn)數(shù)組的最小數(shù)字

    這篇文章主要介紹了求解旋轉(zhuǎn)數(shù)組的最小數(shù)字的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • C語言實現(xiàn)飛機訂票系統(tǒng)

    C語言實現(xiàn)飛機訂票系統(tǒng)

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)飛機訂票系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • C++ 數(shù)據(jù)結(jié)構(gòu)之對稱矩陣及稀疏矩陣的壓縮存儲

    C++ 數(shù)據(jù)結(jié)構(gòu)之對稱矩陣及稀疏矩陣的壓縮存儲

    這篇文章主要介紹了C++ 數(shù)據(jù)結(jié)構(gòu)之對稱矩陣及稀疏矩陣的壓縮存儲的相關(guān)資料,這里實現(xiàn)稀疏矩陣和對稱矩陣的壓縮存儲的實例,需要的朋友可以參考下
    2017-08-08
  • C++ vector越界問題的完整解決方案

    C++ vector越界問題的完整解決方案

    在C++開發(fā)中,std::vector作為最常用的動態(tài)數(shù)組容器,其便捷性與性能優(yōu)勢使其成為處理可變長度數(shù)據(jù)的首選,然而,數(shù)組越界訪問始終是威脅程序穩(wěn)定性的隱形殺手,本文將從越界危害的底層原理出發(fā),系統(tǒng)梳理從基礎(chǔ)防護到現(xiàn)代C++新特性的全方位解決方案,需要的朋友可以參考下
    2025-08-08
  • c語言中的文件加密與解密

    c語言中的文件加密與解密

    這篇文章主要介紹了c語言中的文件加密與解密方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04

最新評論

旬邑县| 绥中县| 大关县| 沅江市| 罗江县| 揭西县| 隆安县| 永城市| 长沙市| 鹿邑县| 乳山市| 公主岭市| 成都市| 永福县| 乐安县| 台山市| 罗山县| 晋江市| 长丰县| 牙克石市| 宁陵县| 手游| 宕昌县| 龙口市| 苍南县| 仁布县| 赤壁市| 廊坊市| 高阳县| 大庆市| 福海县| 孟连| 靖边县| 台州市| 竹溪县| 双牌县| 巴马| 乌鲁木齐市| 突泉县| 吉木萨尔县| 大石桥市|