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

2025-11-19 17:50

本文主要是介绍C++简单日志系统实现代码示例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《C++简单日志系统实现代码示例》日志系统是成熟软件中的一个重要组成部分,其记录软件的使用和运行行为,方便事后进行故障分析、数据统计等,:本文主要介绍C++简单日志系统实现的相关资料,文中通过代码...

前言

生产环境的产品为了稳定性和安全性是不支持开发人员去使用调试器去排查问题的;上线的客户端或产品出现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:
    RollByFphpileSink(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(异步日志器),来实现异步线程+双缓冲区策略。旨在减少生产者与消费者的锁冲突,避免了空间的频繁申请与释放,优化日志的读写性能。

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

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

  • _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)
             javascript       _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:
php    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

这里提供全局日志器的获取接口http://www.chinasem.cn

使用代理模式通过全局函数和宏函数来代理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();
    asyChina编程ncLog();
    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++日志系统内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于C++简单日志系统实现代码示例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156213

相关文章

C#借助Spire.XLS for .NET实现在Excel中添加文档属性

《C#借助Spire.XLSfor.NET实现在Excel中添加文档属性》在日常的数据处理和项目管理中,Excel文档扮演着举足轻重的角色,本文将深入探讨如何在C#中借助强大的第三方库Spire.... 目录为什么需要程序化添加Excel文档属性使用Spire.XLS for .NET库实现文档属性管理Sp

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

Python+FFmpeg实现视频自动化处理的完整指南

《Python+FFmpeg实现视频自动化处理的完整指南》本文总结了一套在Python中使用subprocess.run调用FFmpeg进行视频自动化处理的解决方案,涵盖了跨平台硬件加速、中间素材处理... 目录一、 跨平台硬件加速:统一接口设计1. 核心映射逻辑2. python 实现代码二、 中间素材处

MySQL中between and的基本用法、范围查询示例详解

《MySQL中betweenand的基本用法、范围查询示例详解》BETWEENAND操作符在MySQL中用于选择在两个值之间的数据,包括边界值,它支持数值和日期类型,示例展示了如何使用BETWEEN... 目录一、between and语法二、使用示例2.1、betwphpeen and数值查询2.2、be

python中的flask_sqlalchemy的使用及示例详解

《python中的flask_sqlalchemy的使用及示例详解》文章主要介绍了在使用SQLAlchemy创建模型实例时,通过元类动态创建实例的方式,并说明了如何在实例化时执行__init__方法,... 目录@orm.reconstructorSQLAlchemy的回滚关联其他模型数据库基本操作将数据添

Java数组动态扩容的实现示例

《Java数组动态扩容的实现示例》本文主要介绍了Java数组动态扩容的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1 问题2 方法3 结语1 问题实现动态的给数组添加元素效果,实现对数组扩容,原始数组使用静态分配

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

Python实现快速扫描目标主机的开放端口和服务

《Python实现快速扫描目标主机的开放端口和服务》这篇文章主要为大家详细介绍了如何使用Python编写一个功能强大的端口扫描器脚本,实现快速扫描目标主机的开放端口和服务,感兴趣的小伙伴可以了解下... 目录功能介绍场景应用1. 网络安全审计2. 系统管理维护3. 网络故障排查4. 合规性检查报错处理1.

Go异常处理、泛型和文件操作实例代码

《Go异常处理、泛型和文件操作实例代码》Go语言的异常处理机制与传统的面向对象语言(如Java、C#)所使用的try-catch结构有所不同,它采用了自己独特的设计理念和方法,:本文主要介绍Go异... 目录一:异常处理常见的异常处理向上抛中断程序恢复程序二:泛型泛型函数泛型结构体泛型切片泛型 map三:文

Python轻松实现Word到Markdown的转换

《Python轻松实现Word到Markdown的转换》在文档管理、内容发布等场景中,将Word转换为Markdown格式是常见需求,本文将介绍如何使用FreeSpire.DocforPython实现... 目录一、工具简介二、核心转换实现1. 基础单文件转换2. 批量转换Word文件三、工具特性分析优点局