当前位置: 代码网 > it编程>编程语言>C/C++ > C++简单日志系统实现代码示例

C++简单日志系统实现代码示例

2025年11月19日 C/C++ 我要评论
前言生产环境的产品为了稳定性和安全性是不支持开发人员去使用调试器去排查问题的;上线的客户端或产品出现bug无法复现并解决时;在分布式、多线程/多进程代码中问题难以定位;开发人员就可以通过日志系统打印的

前言

生产环境的产品为了稳定性和安全性是不支持开发人员去使用调试器去排查问题的;上线的客户端或产品出现bug无法复现并解决时;在分布式、多线程/多进程代码中问题难以定位;开发人员就可以通过日志系统打印的日志来排查问题并及时解决。

同样也可以帮助新人了解开发人员理解的运行逻辑。

util.hpp

实现工具类:用于实现通用功能,供logmsg(日志内容封装模块)以及sink(日志落地模块)调用。

获取当前系统时间

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;
    }
    ...
};

获取文件的当前路径

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);
    }
    ...
};

创建目录

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(日志内容封装模块)调用。

定义日志等级: info debug warn error fatal off。

class loglevel{
public:
    //日志等级
    enum class value{
        unkown = 0,
        info,
        debug,
        warn,
        error,
        fatal,
        off
    };
    ...
};

转换字符串接口:将相应的日志等级输出成字符串。

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

日志内容封装类:用于实现日志内容的封装,以供format.hpp(日志格式化模块)。

struct logmsg
{
    time_t _ctime;          //日志产生的时间戳
    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

日志格式化类:主要负责日志的格式化,用于自定义格式,由格式化std::vector<formatitem>来对格式进行存储管理,需要使用到logmsg(日志封装模块)

其中给定了占位符,以及其将其替换的内容:

  • %d:日期 ----子格式{%h:%m:%s}。

  • %t:线程id。

  • %t:缩进。

  • %p:日志等级。

  • %c:日志器名称。

  • %f:文件名。

  • %l:行号。

  • %m:日志信息。

  • %n:换行。

formatitem类主要负责⽇志消息⼦项的获取及格式化。

其包含以下⼦类:

  • timeformatitem:从logmsg中取出日志产生的系统时间

  • fileformatitem:从logmsg中取出源码文件名

  • lineformatitem:从logmsg中取出源码中调用日志的行号

  • levelformatitem:从logmsg中取出日志的等级

  • threadformatitem:从logmsg中取出调用日志的线程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类是对⽇志消息按格式化整理后输出的执行者。

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());
    }
    //格式化后输出到缓冲区
    void format(std::ostream &out, const logmsg &msg){
        for (auto &fmt : _format_item)
            fmt->format(out, msg);
    }
    //格式化日志消息后返回内容
    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():解析传入的自定义格式,并将格式及其对应格式化子类指针填入std::vector<formatitem>

formatitem::ptr createformatitem():用于辅助parseformat()填入格式对应的指针。

class formator{
    ...
private:
    //解析格式化规则字符串
    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;
    }

    // 创建格式化子项
    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 << "没有对应的格式化字符: %" << key << std::endl;
        abort();
        return nullptr;
    }
    ...
};

sink.hpp

日志落地类:将日志输出到指定的模块,采用简单工厂模式,这里给出三种落地模式,支持拓展。

用户可以继承基类logsink来自定义实现一个落地方式。

class logsink{
public:
    using ptr = std::shared_ptr<logsink>;
    virtual void log(const char *data, size_t len) = 0;
    virtual ~logsink() {}
};
提供一个log接口供子类实现,用于日志落地。

标准输出:stdoutsink,将日志输出到标准输出。

class stdoutsink : public logsink{
public:
    // 将日志输出到标准输出
    void log(const char *data, size_t len) override{
        std::cout.write(data, len);
    }
};

指定文件:将日志输出到指定文件。

  1. 查看文件是否存在,并创建文件。

  2. 获取文件句柄。

class filesink : public logsink{
public:
    filesink(const std::string &pathname) : _pathname(pathname){
        // 查看文件是否存在,并创建文件
        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;    // 文件句柄
};

滚动文件(按大小):将日志输出到滚动文件。其思想可分为时间和大小,这里实现了按大小滚动。

  1. 按文件大小滚动,超过1g时会创建新文件,并写入新文件。

  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:
    // 进行大小判断,创建新文件
    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();
        // 创建文件
        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; // 记录当前已写入的数据大小
};
传入落地的方式即可--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

日志缓冲区类:此类配合looper中的asynclooper(异步日志器),来实现异步线程+双缓冲区策略。旨在减少生产者与消费者的锁冲突,避免了空间的频繁申请与释放,优化日志的读写性能。

  • 其中日志器将消息写到一个日志缓冲区,日志缓存区再与任务缓冲区交互,由异步线程将任务缓冲区中的日志消息写到磁盘。

  • _default_size:初始化缓冲区大小。

  • _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){}

    //写入数据
    void push(const char* data,size_t len){
        //空间不够则扩容
        ensureenough(len);

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

    //返回可读数据的起始地址
    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(){
        //只为固定大小缓冲区提供的接口
        return (_buffer.size() - _write_idx);
    }

    //交换缓冲区
    void swap(buffer &buffer){
        _buffer.swap(buffer._buffer);
        std::swap(_write_idx,buffer._write_idx);
        std::swap(_read_idx,buffer._read_idx);
    }

    //重置缓冲区
    void reset(){
        _write_idx = 0;
        _read_idx = 0;
    }

    //缓冲区是否为空
    bool empty(){
        return (_write_idx == _read_idx);
    }
private:
    //判断缓冲区大小是否足够
    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

异步工作器:提供停止工作器,以及写入缓冲区两个接口,内置两个缓冲器,内含异步线程入口函数。内部提供一个回调函数指针,用于异步工作线程回调使用。

提供两种缓存冲区策略:

  • async_safe:安全策略,当缓冲区满时阻塞写入缓冲区,防止资源耗尽。

  • async_unsafe:缓冲区无限扩容策略。

using functor = std::function<void(buffer &)>;
enum class asynctype
{
    async_safe,     //安全状态,缓冲区满就阻塞,防止资源耗尽
    async_unsafe    //缓冲区无限扩容
};

// 异步日志器
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);
        //写入长度小于缓冲区可写长度才可写,否则阻塞
        if(_runtype == asynctype::async_safe)
            _cond_pro.wait(lock, [&](){ return len <= _pro_buffer.writeablesize();});

        _pro_buffer.push(data,len);

        //唤醒消费者 处理数据
        _cond_con.notify_one();
    }

private:
    //线程入口函数
    void threadenter()
    {
        while(1)
        {
            //给互斥锁设定作用域
            {
                std::unique_lock<std::mutex> lock(_mutex);
                //已设置退出状态并且缓冲区为空,则退出
                if(!_running && _pro_buffer.empty())break;

                //程序结束前 或 生产者缓冲区不为空,进行数据写入
                _cond_con.wait(lock, [&](){ return !_running || !_pro_buffer.empty();});

                _con_buffer.swap(_pro_buffer);
                //唤醒生产者
                if(_runtype == asynctype::async_safe)
                    _cond_pro.notify_all();
            }
            //回调处理
            _callback(_con_buffer);
            //初始化消费缓冲区
            _con_buffer.reset();
        }
    }
private:
    // 回调函数 异步工作器使用者传入
    functor _callback;

private:
    asynctype _runtype;
    bool _running;
    std::mutex _mutex;
    //生产缓冲区
    buffer _pro_buffer;
    //消费缓冲区
    buffer _con_buffer;
    std::condition_variable _cond_pro;
    std::condition_variable _cond_con;
    std::thread _thread;
};

logger.hpp

日志类:使用建造者模式,来简化使用难度。这个类主要用来与前端交互,需要打印日志时,只需创建logger对象,并调用debug、info、warn等方法输出日志即可。支持解析可变参数列表和输出格式,可像printf一样输出日志。

  • 这里提供两种日志的输出方式:同步输入日志以及异步输出日志。

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

  • 异步输出:将日志写入到缓冲区中,由异步工作线程来完成日志输出。

因此,设计基类logger,由子类synclogger(同步输出)以及asynclogger(异步输出)来继承。设置log接口由子类重写即可。

支持传入多个日志落地模块,实现多种落地方式共同执行。

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())
    {
    }
    // 完成对不同等级日志的构造
    void debug(const std::string &file, size_t line, const std::string &fmt, ...){
        // 1.判断日志输出等级
        if (loglevel::value::debug < _limit_level)
            return;

        // 2.对不定参数解包
        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.构造logmsg
        logmsg logmsg(level, line, file, _logger_name, str);

        // 4.调用格式化工具格式化logmsg
        std::stringstream ss;
        _formator->format(ss, logmsg);

        // 5.调用log输出
        log(ss.str().c_str(), ss.str().size());
    }
    //由子类实现输出逻辑
    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)
        ) //构造时,传入回调函数构造异步日志器
    {
    }

    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; //异步日志器
};

日志器建造者模式设计:

由于logger是对多个模块的整合,想要创建日志器需要多个参数,使用起来相对麻烦,因此设计建造者模式来降低使用难度。

日志建造者基类:

//日志器建造者
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);
    }

    // 总建造函数由子类继承重写
    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:
    // 重写父类建造函数
    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);
    }
};

局部日志器的使用会受到作用域的限制,我们希望在全局各个地方都可以调用日志器进行输出,因此设计了一个日志器管理的单例类,这样我们就可以在任何地方调用这个管理器来获取单例对象,来对日志进行打印。

我们基于日志类,继承出一个全局日志器建造者类,建造出来的全局日志器直接添加到单例日志管理器中。

日志器管理器:

//日志器管理器  --默认日志器使用局部建造者创建并由管理器管理
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

这里提供全局日志器的获取接口。

使用代理模式通过全局函数和宏函数来代理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

测试环境:

  • 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.创建指定数量的线程
    std::vector<std::thread> threads;
    std::vector<double> threads_cost(thr_cnt,0);
    //每个线程需要输出的日志数量
    size_t msg_per_thr = msg_cnt / thr_cnt;
    for(int i = 0; i < thr_cnt; i++)
    {
        threads.emplace_back([&,i](){
            //4.线程函数内计时
            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.结束计时
            auto end = std::chrono::high_resolution_clock::now();
            std::chrono::duration<double> cost = end - start;
            threads_cost[i] = cost.count();
            cout << "线程" << i << "\t输出日志数量: " << msg_per_thr <<", 耗时: " << cost.count() << "s" <<endl;
        });
    }
    
    for(int i = 0; i < thr_cnt; i++)
        threads[i].join();

    //7.计算总耗时(多线程并发,以线程最长耗时,为总耗时)
    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每秒输出日志数量: " << 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;
}

测试结果:

同步日志:

测试日志: 1000000条,总大小: 97656kb

线程1 输出日志数量: 333333, 耗时: 0.842788s

线程0 输出日志数量: 333333, 耗时: 0.862125s

线程2 输出日志数量: 333333, 耗时: 0.874029s

总耗时: 0.874029s

每秒输出日志数量: 1144126条

每秒输出日志大小: 111731kb

异步日志:

测试日志: 1000000条,总大小: 97656kb

线程0 输出日志数量: 333333, 耗时: 0.246758s

线程1 输出日志数量: 333333, 耗时: 0.247231s

线程2 输出日志数量: 333333, 耗时: 0.250321s

总耗时: 0.250321s

每秒输出日志数量: 3994875条

每秒输出日志大小: 390124kb

总结

到此这篇关于c++简单日志系统实现的文章就介绍到这了,更多相关c++日志系统内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com