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函数的功能是,将字符串中的内容进行日志消息格式化,并进行落地操作
cpp 复制代码
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; // 落地方向数组
};

同步日志器类设计

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

  • 遍历日志落地数组,以数组中的各种落地方式进行落地操作;
cpp 复制代码
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);
        }
    }
};

同步日志器测试

cpp 复制代码
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;
}

日志器建造者模式设计

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

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

设计思想:

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

抽象日志器建造者类

  • 建造者类中包含成员:
    • logger_type 日志器类型;
    • logger_name 日志器名称;
    • limit_level 日志输出限制等级;
    • formatter 格式化对象;
    • sinks 日志落地数组;
  • 还有构建各个零件的函数;
cpp 复制代码
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;
};

派生局部日志器建造者

cpp 复制代码
/*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);
    }
};

日志器建造者类测试

cpp 复制代码
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;
}

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

cpp 复制代码
#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "looper.hpp"
#include <cstdarg>
#include <atomic>
#include <thread>
#include <mutex>
#include <unordered_map>

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
相关推荐
唐诺3 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨4 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客4 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin4 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos6 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室6 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0016 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我586 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc7 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很7 小时前
C++ 集合 list 使用
c++