• C++项目实战——基于多设计模式下的同步&异步日志系统-⑨-同步日志器类与日志器建造者类设计


    专栏导读

    🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

    🌸专栏简介:本文收录于 C++项目——基于多设计模式下的同步与异步日志系统

    🌸相关专栏推荐:C语言初阶系列C语言进阶系列 C++系列数据结构与算法Linux

    在这里插入图片描述
    日志器主要是用来与前端交互,当我们需要使用日志系统打印日志消息时,只需要创建Logger对象,调用该对象的debuginfowarnerrorfatal等方法输出自己想要打印的日志消息即可。支持解析可变参数列表和输出格式,就可以做到像printf函数一样打印日志。

    因为日志器模块是对前边所有模块的一个整合,所以Logger类管理的成员有:

    • 日志器名称(日志器的唯一标识);
    • 格式化模块对象(Formatter);
    • 落地模块对象数组(一个日志器可能会向多个位置进行日志输出);
    • 默认的输出限制等级(控制达到指定等级的日志才可以输出);
    • 互斥锁(保证日志输出过程是线程安全的,不会出现交叉日志);

    Logger类提供的操作有:

    • debug等级日志的输出操作;
    • info等级日志的输出操作;
    • warn等级日志的输出操作;
    • error等级日志的输出操作;
    • fatal等级日志的输出操作;

    当前日志系统支持同步日志和异步日志两种方式,两个不同的日志器唯一的区别是它们在日志落地方式上有所不同:

    • 同步日志器:直接对日志消息进行输出;
    • 异步日志器:将日志消息放入缓冲区,由异步线程进行输出。

    因此日志器在设计的时候先设计一个Logger基类,在Logger基类的基础上继承出SyncLogger同步日志器AsyncLogger异步日志器

    Logger类设计

    • debuginfo等接口在设计时,需要传递参数有文件名、行号、参数包。至于为什么要传递文件名与行号,因为要避免获取文件名和行号时是在本函数内部;
    • 将参数包进行内容提取后保存在字符串中,交由serialize进行处理;
    • serialize函数的功能是,将字符串中的内容进行日志消息格式化,并进行落地操作
    class Logger
    {
    public:
        using ptr = std::shared_ptr<Logger>;
    
        Logger(const std::string &logger_name,
               LogLevel::value level,
               Formatter::ptr &formatter,
               std::vector<LogSink::ptr> &sinks) : 
               _logger_name(logger_name),
               _limit_level(level),
               _formatter(formatter),
               _sinks(sinks.begin(), sinks.end())
        {}
    	// 获取日志器名称
        const std::string& name(){ return _logger_name; }
        
        void debug(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            // 判断当前的日志是否达到了输出等级
            if (LogLevel::value::DEBUG < _limit_level)
            {
                return;
            }
    
            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            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, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::INFO < _limit_level)
            {
                return;
            }
    
            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            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::INFO, file, line, res);
            free(res);
        }
        void warn(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::WARN < _limit_level)
            {
                return;
            }
    
            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            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::WARN, file, line, res);
            free(res);
        }
        void error(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::ERROR < _limit_level)
            {
                return;
            }
    
            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            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::ERROR, file, line, res);
            free(res);
        }
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::FATAL < _limit_level)
            {
                return;
            }
    
            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            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::FATAL, file, line, res);
            free(res);
        }
    
    protected:
        void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
        {
            // 构造LogMsg对象
            LogMsg msg(level, line, file, _logger_name, str);
            // 通过格式化工具对LogMsg进行格式化, 得到格式化后的日志字符串
            std::stringstream ss;
            _formatter->format(ss, msg);
            // 对日志进行落地
            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; // 限制输出等级
        Formatter::ptr _formatter;
        std::vector<LogSink::ptr> _sinks; // 落地方向数组
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149

    同步日志器类设计

    同步日志器设计较为简单,设计思想是:

    • 遍历日志落地数组,以数组中的各种落地方式进行落地操作;
    class SyncLogger : public Logger
    {
    public:
        SyncLogger(const std::string &logger_name,
                   LogLevel::value level,
                   LOG::Formatter::ptr &formatter,
                   std::vector<LogSink::ptr> &sinks) 
                   : Logger(logger_name, level, formatter, sinks)
        {
        }
    
    protected:
        void log(const char *data, size_t len)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_sinks.empty())
                return;
            for (auto &sink : _sinks)
            {
                sink->log(data, len);
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    同步日志器测试

    int main()
    {
    	LOG::LogMsg msg(LOG::LogLevel::value::INFO, 53, "main.cc", "root", "格式化功能测试...");
        LOG::Formatter fmt;
        std::string str = fmt.format(msg);
        LOG::LogSink::ptr time_lsp = LOG::SinkFactory::create<RollByTimeSink>("./logfile/roll-", TimeGap::GAP_SECOND);
        time_t old = LOG::util::Date::getTime();
        while(LOG::util::Date::getTime() < old + 5)
        {
            time_lsp->log(str.c_str(), str.size());
            sleep(1);
        }
    
        std::string logger_name = "sync_logger";
        LOG::LogLevel::value limit = LOG::LogLevel::value::WARN;
        LOG::Formatter::ptr fmt(new LOG::Formatter("[%d{%H:%M:%S}][%c][%f:%l][%p]%T%m%n"));
        LOG::LogSink::ptr stdout_lsp = LOG::SinkFactory::create<LOG::StdOutSink>();
        LOG::LogSink::ptr file_lsp = LOG::SinkFactory::create<LOG::FileSink>("./logfile/test.log");
        LOG::LogSink::ptr roll_lsp = LOG::SinkFactory::create<LOG::RollBySizeSink>("./logfile/test.log", 1024*1024);
        std::vector<LOG::LogSink> sinks = {stdout_lsp, file_lsp, roll_lsp};
        LOG::Logger::ptr logger(new LOG::SyncLogger(logger_name, limit, fmt, sinks));
    
        logger->debug(__FILE__, __LINE__, "%s", "测试日志");
        logger->info(__FILE__, __LINE__, "%s", "测试日志");
        logger->warn(__FILE__, __LINE__, "%s", "测试日志");
        logger->error(__FILE__, __LINE__, "%s", "测试日志");
        logger->fatal(__FILE__, __LINE__, "%s", "测试日志");
    
        size_t cursize = 0, count = 0;
        while(cursize < 1024*1024*10)
        {   
            logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
            cursize+=20;
        }
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    日志器建造者模式设计

    观察上一小节中的日志器测试代码,在构建一个同步日志器时,需要先设置很多的零部件。这对于用户来说未免有些繁琐

    我们需要使用建造者模式来建造日志器,而不要让用户直接去构造日志器,以简化用户的使用复杂度

    设计思想:

    • 抽象一个日志器建造者类
      • 设置日志器类型;
      • 将不同类型(同步&异步)日志器的创建放到同一个日志器建造者类中完成。
    • 派生出具体的建造者类----局部日志器建造者 & 全局日志器建造者类(后面添加了全局单例管理器之后,将日志器添加全局管理)。

    抽象日志器建造者类

    • 建造者类中包含成员:
      • logger_type 日志器类型;
      • logger_name 日志器名称;
      • limit_level 日志输出限制等级;
      • formatter 格式化对象;
      • sinks 日志落地数组;
    • 还有构建各个零件的函数;
    enum class LoggerType
    {
        LOGGER_SYNC,
        LOGGER_ASYNC
    };
    // 1.抽象一个日志器建造者类(完成日志器所需零部件的构建 & 日志器的构建)
    class LoggerBuilder
    {
    public:
        LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC),
                          _limit_level(LogLevel::value::DEBUG)
        {}
        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 buildFormatter(const std::string &pattern)
        {
            _formatter = std::make_shared<Formatter>(pattern);
        }
        template <typename SinkType, typename... Args>
        void buildSink(Args &&...args)
        {
            LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
            _sinks.push_back(psink);
        }
        virtual Logger::ptr build() = 0;
    
    protected:
        LoggerType _logger_type;
        std::string _logger_name;
        std::atomic<LogLevel::value> _limit_level;
        Formatter::ptr _formatter;
        std::vector<LogSink::ptr> _sinks;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    派生局部日志器建造者

    /*2.派生出具体的建造者类---局部日志器的建造者 & 全局日志器的建造者*/
    class LocalLoggerBuilder : public LoggerBuilder
    {
    public:
        Logger::ptr build() override
        {
            assert(_logger_name.empty() == false);
            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, _limit_level, _formatter, _sinks);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    日志器建造者类测试

    int main()
    {
    	std::unique_ptr<LOG::LoggerBuilder> builder(new LOG::GlobalLoggerBuilder());
        builder->buildLoggerName("sync_logger");
        builder->buildLoggerLevel(LOG::LogLevel::value::WARN);
        builder->buildFormatter("[%c][%f:%l]%m%n");
        builder->buildLoggerType(LOG::LoggerType::LOGGER_SYNC);
        builder->buildEnableUnSafeAsync();
        builder->buildSink<LOG::FileSink>("./logfile/async.log");
        builder->buildSink<LOG::StdOutSink>();
        LOG::Logger::ptr = builder->build();
    
        logger->debug(__FILE__, __LINE__, "%s", "测试日志");
        logger->info(__FILE__, __LINE__, "%s", "测试日志");
        logger->warn(__FILE__, __LINE__, "%s", "测试日志");
        logger->error(__FILE__, __LINE__, "%s", "测试日志");
        logger->fatal(__FILE__, __LINE__, "%s", "测试日志");
    
        size_t cursize = 0, count = 0;
        while(cursize < 1024*1024*10)
        {   
            logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
            cursize+=20;
        }
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    同步日志器类与日志器建造者类整理

    #ifndef __M_LOGGER_H__
    #define __M_LOGGER_H__
    #include "util.hpp"
    #include "level.hpp"
    #include "format.hpp"
    #include "sink.hpp"
    #include "looper.hpp"
    #include 
    #include 
    #include 
    #include 
    #include 
    
    namespace LOG
    {
        class Logger
        {
        public:
            using ptr = std::shared_ptr<Logger>;
    
            Logger(const std::string &logger_name,
                   LogLevel::value level,
                   Formatter::ptr &formatter,
                   std::vector<LogSink::ptr> &sinks) : 
                   _logger_name(logger_name),
                   _limit_level(level),
                   _formatter(formatter),
                   _sinks(sinks.begin(), sinks.end())
            {
            }
    
            const std::string& name(){ return _logger_name; }
            void debug(const std::string &file, size_t line, const std::string &fmt, ...)
            {
                // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
                // 判断当前的日志是否达到了输出等级
                if (LogLevel::value::DEBUG < _limit_level)
                {
                    return;
                }
    
                // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
                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, ...)
            {
                // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
                if (LogLevel::value::INFO < _limit_level)
                {
                    return;
                }
    
                // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
                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::INFO, file, line, res);
                free(res);
            }
            void warn(const std::string &file, size_t line, const std::string &fmt, ...)
            {
                // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
                if (LogLevel::value::WARN < _limit_level)
                {
                    return;
                }
    
                // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
                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::WARN, file, line, res);
                free(res);
            }
            void error(const std::string &file, size_t line, const std::string &fmt, ...)
            {
                // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
                if (LogLevel::value::ERROR < _limit_level)
                {
                    return;
                }
    
                // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
                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::ERROR, file, line, res);
                free(res);
            }
            void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
            {
                // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
                if (LogLevel::value::FATAL < _limit_level)
                {
                    return;
                }
    
                // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
                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::FATAL, file, line, res);
                free(res);
            }
    
        protected:
            void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
            {
                // 构造LogMsg对象
                LogMsg msg(level, line, file, _logger_name, str);
                // 通过格式化工具对LogMsg进行格式化, 得到格式化后的日志字符串
                std::stringstream ss;
                _formatter->format(ss, msg);
                // 对日志进行落地
                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; // 限制输出等级
            Formatter::ptr _formatter;
            std::vector<LogSink::ptr> _sinks;
        };
    
        class SyncLogger : public Logger
        {
        public:
            SyncLogger(const std::string &logger_name,
                       LogLevel::value level,
                       LOG::Formatter::ptr &formatter,
                       std::vector<LogSink::ptr> &sinks) 
                       : Logger(logger_name, level, formatter, sinks)
            {
            }
    
        protected:
            void log(const char *data, size_t len)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                if (_sinks.empty())
                    return;
                for (auto &sink : _sinks)
                {
                    sink->log(data, len);
                }
            }
        };
        // 1.抽象一个日志器建造者类(完成日志器所需零部件的构建 & 日志器的构建)
        //  1.设置日志器类型
        //  2.将不同类型的日志器的创建放到同一个日志器建造者类中完成
        enum class LoggerType
        {
            LOGGER_SYNC,
            LOGGER_ASYNC
        };
    
        class LoggerBuilder
        {
        public:
            LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC),
                              _limit_level(LogLevel::value::DEBUG)
            {}
            void buildLoggerType(LoggerType type) { _logger_type = type; }
            void buildEnableUnSafeAsync() { _looper_type = AsyncType::ASYNC_UNSAFE; }
            void buildLoggerName(const std::string &name) { _logger_name = name; }
            void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
            void buildFormatter(const std::string &pattern)
            {
                _formatter = std::make_shared<Formatter>(pattern);
            }
            template <typename SinkType, typename... Args>
            void buildSink(Args &&...args)
            {
                LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
                _sinks.push_back(psink);
            }
            virtual Logger::ptr build() = 0;
    
        protected:
            LoggerType _logger_type;
            std::string _logger_name;
            std::atomic<LogLevel::value> _limit_level;
            Formatter::ptr _formatter;
            std::vector<LogSink::ptr> _sinks;
        };
    
        /*2.派生出具体的建造者类---局部日志器的建造者 & 全局日志器的建造者*/
        class LocalLoggerBuilder : public LoggerBuilder
        {
        public:
            Logger::ptr build() override
            {
                assert(_logger_name.empty() == false);
                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, _limit_level, _formatter, _sinks);
            }
        };
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251

    在这里插入图片描述

  • 相关阅读:
    攻防演习防御体系构建之第一篇之介绍和防守的四个阶段
    算法训练营day17
    数仓分层设计及数据同步问题,,220728,,,,
    9月18日,每日信息差
    [Spring cloud alibaba][Sentinel][Gateway] 微服务整合sentinel流控未发现注册服务(监控空白问题)
    基于Django+Vue开发的社区疫情管理系统(附源码)
    OpenCV 卷积运算和卷积核
    Open3D 生成空间圆点云
    利用熵权法进行数值评分计算——算法过程
    一款构建Python命令行应用的开源库
  • 原文地址:https://blog.csdn.net/gllll_yu/article/details/132862953