基于多设计模式的同步&异步日志系统--代码设计(四)

日志器模块设计(logger.hpp)

        日志器模块是对前述几个模块的整合,实现对日志信息的格式化与落地等功能。这里设计同步和异步两种日志器。

        一个日志器所要包含的元素有:

  • 日志器名称:唯一表示日志器。
  • 日志器等级:限制日志输出的最低等级。
  • 格式化工具:用于格式化日志信息。
  • 日志落地方向数组:用于将日志落地到相应位置。
  • 互斥锁:为了支持高并发,需要一个互斥锁保证日志信息的正确。

        需要提供的对外接口接口有:

  • void debug(const std::string &file, const size_t &line, const std::string fmt, ...)

  • void info(const std::string &file, const size_t &line, const std::string fmt, ...)

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

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

  • void fatal(const std::string &file, const size_t &line, const std::string fmt, ...)

说明:这五种接口(对应物种日志等级)需要用户传入文件名,文件行号,日志主体信息(其他信息在日志器构造时就会确认),先将日志的主体信息(不定参信息)按照fmt转换为字符串,再用所有信息构造一个LogMsg,格式化后进行日志落地。

        需要的对内接口:

  • void log(const char *data, size_t len) 说明:这是最后用于落地日志的接口,同步异步两周日志器的落地方向可能一致,但是落地方式一定不一致,基于这一点,我们也要创建一个日志器基类,将此接口设置为虚函数,交由子类实现。

  • serialize(LogLevel::value level, const std::string &file, const size_t &line, char *str) 说明:用于格式化日志信息的接口,在最后调用log进行日志落地。

基类日志器

        由于对外的五种接口与serialize接口对于每一种日志器来说都是一样的,因此我们可将其在基类中实现,又因为五种接口的区别只是日志等级的不同,极为相似,所有在下面的代码中只展示出debug的实现细节,其他基本一致。

代码实现:

    class Logger
    {
    public:
        using ptr = std::shared_ptr;

        Logger(const std::string &logger_name,
               LogLevel::value limit_level,
               Formatter::ptr &formatter,
               std::vector &sinks)
            : _logger_name(logger_name),
              _limit_level(limit_level),
              _formatter(formatter),
              _sinks(sinks.begin(), sinks.end())
        {
        }

        void debug(const std::string &file, const size_t &line, const std::string fmt, ...)
        {
            // 判断当前日志是否达到了输出等级
            if (LogLevel::value::DEBUG < _limit_level)
                return;
            // 对数据进行格式化,得到日志消息字符串
            char *res;
            va_list vl;
            va_start(vl, fmt);
            int len = vasprintf(&res, fmt.c_str(), vl);
            va_end(vl);
            if (len == -1)
            {
                std::cerr << "vasprintf error!" << std::endl;
                return;
            }
            serialize(LogLevel::value::DEBUG, file, line, res);
            free(res);
        }
    protected:
        void serialize(LogLevel::value level, const std::string &file, const size_t &line, char *str)
        {
            // 构造LogMsg
            LogMsg msg(level, line, file, _logger_name, str);
            // 格式化LogMsg
            std::string message = _formatter->format(msg);
            // 日志落地
            log(message.c_str(), message.size());
        }
        virtual void log(const char *data, size_t len) = 0;

    protected:
        std::mutex _mutex;
        std::string _logger_name;
        std::atomic _limit_level;
        Formatter::ptr _formatter;
        std::vector _sinks;
    };

同步日志器

        同步日志器是指完成业务逻辑的线程与实现日志落地的线程是一个线程,因此它的log实现逻辑十分简单,直接调用_sinks中的落地函数即可。

代码实现:

    class SyncLogger : public Logger
    {
    public:
        SyncLogger(const std::string &logger_name,
                   LogLevel::value limit_level,
                   Formatter::ptr &formatter,
                   std::vector &sinks)
            : Logger(logger_name, limit_level, formatter, sinks)
        {
        }

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

异步日志器

        异步日志器是指完成业务逻辑的线程与实现日志落地的线程不是一个线程,可避免日志输出阻塞而影响业务功能的情况,由于涉及到多线程,因此实现起来较为复杂,下一篇中我们再详细讲解。

建造日志器

        由于构建一个日志器所需要的部件很多,先让用户自己先创建部件再创建日志器显然不合理,因此我们可以用建造者模式来建造日志器。

        可以发现在建造日志器时有两个特点:

  • 构建各个部件没有先后顺序:不需要指挥者类。
  • 构建日志落地方向不固定:不能用指挥者类。

        因此我们便不需要设计一个指挥者。

下面先给出代码再进行解释:

首先是日志器建造者基类:

    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 buildLoggerName(const std::string &name) { _logger_name = name; }
        void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
        void buildLoggerFormatter(const std::string &pattern)
        {
            _formatter = std::make_shared(pattern);
        }
        template 
        void buildLoggerSink(Args &&...args)
        {
            //LogSink::ptr sink = std::make_shared(std::forward(args)...);
            LogSink::ptr sink = SinkFactory::createSink(std::forward(args)...);
            _sinks.push_back(sink);
        }
        virtual Logger::ptr build() = 0;

    protected:
        LoggerType _logger_type;
        std::string _logger_name;
        std::atomic _limit_level;
        Formatter::ptr _formatter;
        std::vector _sinks;
    };

 说明:

  • 与日志器相比,添加了一个LoggerType的枚举类型,用来指定建造那种日志器(默认为同步日志器)。
  • 对于type,name,level,由用户直接传入,然后赋值即可。
  • 对于formatter,不需要用户自己构建一个格式化工具,只需要传入一个格式化规则,然后有建造者构建即可。
  • 对于落地方向,由于每种方向的参数不同,我们将其设计为一个不定参模版函数,和之前的生产落地方向的工厂类似。
  • build接口:用所有部件创建一个日志器并且返回,交给具体的派生类实现。

派生类具体实现:

    class LocalLoggerBuilder : public LoggerBuilder
    {
    public:
        Logger::ptr build() override
        {
            assert(!_logger_name.empty());
            if(_formatter == nullptr)
            {
                _formatter = std::make_shared();
            }
            if(_sinks.empty())
            {
                _sinks.push_back(std::make_shared());
            }
            if(_logger_type == LoggerType::LOGGER_ASYNC)
            {

            }
            return std::make_shared(_logger_name, _limit_level, _formatter, _sinks);
        }
    };
}

说明:

  • 在构建日志器前先对部件进行检查:
    • 日志器名称必须要有。
    • 格式化工具没有指定就采取默认的。
    • 落地方向没有添加就填充一个标准输出。
    • 日志限制等级已有默认。
    • 日志器类型已有默认。
  • 构建相应日志器名返回其智能指针。

 

你可能感兴趣的:(日志系统,设计模式)