easylogging++的那些事(四)源码分析(二)日志记录宏(一)CLOG宏(五)其他相关类

在上一篇中我们分析了 CLOG日志信息保存 的流程,今天我们看看前面 CLOG 宏所使用到的一些相关类。

el::base::Writer类

    另一个 construct 接口

    //声明
    Writer &construct(Logger *logger, bool needLock = true);
    // 定义
    Writer &Writer::construct(Logger *logger, bool needLock)
    {
        m_logger = logger;
        // initializeLogger前面已经介绍过了,这里就不多说了
        initializeLogger(logger->id(), false, needLock);
        // 初始化m_messageBuilder
        m_messageBuilder.initialize(m_logger);
        return *this;
    }

    这个接口可以使用 Logger 作为参数来初始化 Writer 对象,而不必使用 Logger ID 作为参数在 RegisteredLoggers 当中查找,提高效率。

    这个接口主要用于使用 Logger 的类 printf 接口记录日志的场景中,后面介绍到这一块时,会详细分析。

el::base::NullWriter类

    这个类重载的两个输出运算符就直接返回了对象本身,什么也没做,也就相当于不输出日志了。

    class NullWriter : base::NoCopy
    {
    public:
        NullWriter(void) {}
    
        // Null manipulator
        inline NullWriter &operator<<(std::ostream &(*)(std::ostream &))
        {
            return *this;
        }
    
        template <typename T>
        inline NullWriter &operator<<(const T &)
        {
            return *this;
        }
    
        inline operator bool()
        {
            return true;
        }
    };

el::LogMessage类

    主要用于作为其他类的成员或者函数参数,日志相关信息的 wrapper 类,都是一些成员函数的获取接口,没什么可说的。

    class LogMessage
    {
    public:
        LogMessage(Level level, const std::string &file, base::type::LineNumber line, const std::string &func,
                   base::type::VerboseLevel verboseLevel, Logger *logger) : m_level(level), m_file(file), m_line(line), m_func(func),
                                                                            m_verboseLevel(verboseLevel), m_logger(logger), m_message(logger->stream().str())
        {
        }
        inline Level level(void) const
        {
            return m_level;
        }
        inline const std::string &file(void) const
        {
            return m_file;
        }
        inline base::type::LineNumber line(void) const
        {
            return m_line;
        }
        inline const std::string &func(void) const
        {
            return m_func;
        }
        inline base::type::VerboseLevel verboseLevel(void) const
        {
            return m_verboseLevel;
        }
        inline Logger *logger(void) const
        {
            return m_logger;
        }
        inline const base::type::string_t &message(void) const
        {
            return m_message;
        }
    
    private:
        Level m_level;                           // 日志级别
        std::string m_file;                      // 日志所在的源文件
        base::type::LineNumber m_line;           // 日志所在的行号
        std::string m_func;                      // 日志所在的函数
        base::type::VerboseLevel m_verboseLevel; // 日志对应的详细级别
        Logger *m_logger;                        // 日志对应的日志记录器
        base::type::string_t m_message;          // 日志的内容部分(消息格式指示符当中的%msg代表的内容)
    };

el::Logger类

    el:: Logger 被 el:: base:: writer 类使用,用于读取配置,不直接写日志
    在 easylogging++的总体设计 中介绍过 el:: Logger 的一些成员变量,这里分析其部分接口。

isValidId接口

    static const char *kValidLoggerIdSymbols =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._";
    
    // 检验 logger id 是否有效
    bool Logger::isValidId(const std::string &id)
    {
        for (std::string::const_iterator it = id.begin(); it != id.end(); ++it)
        {
            if (!base::utils::Str::contains(base::consts::kValidLoggerIdSymbols, *it))
            {
                return false;
            }
        }
        return true;
    }

    base:: utils::Str 是 easylogging++内部封装的字符操作的工具类,日志记录器的 id 必须是 kValidLoggerIdSymbols 当中的字符

flush接口

    1、 void Logger:: flush(void);

    // 刷新日志记录器的所有日志级别的日志文件
    void Logger::flush(void)
    {
        ELPP_INTERNAL_INFO(3, "Flushing logger [" << m_id << "] all levels");
        base::threading::ScopedLock scopedLock(lock());
        base::type::EnumType lIndex = LevelHelper::kMinValid;
        LevelHelper::forEachLevel(&lIndex, [&](void) -> bool
                                  {
                                      flush(LevelHelper::castFromInt(lIndex), nullptr);
                                      return false; // 返回false是为了让每个日志级别都能够flush
                                  });
    }

    上面 LevelHelper:: forEachLevel 的实现如下:

    // 从指定级别开始遍历,对于每个级别执行一些操作(fn)
    void LevelHelper::forEachLevel(base::type::EnumType *startIndex, const std::function<bool(void)> &fn)
    {
        base::type::EnumType lIndexMax = LevelHelper::kMaxValid;
        do
        {
            if (fn())
            {
                break;
            }
            *startIndex = static_cast<base::type::EnumType>(*startIndex << 1);
        } while (*startIndex <= lIndexMax);
    }

    //flush中调用LevelHelper::forEachLevel传递的fn为匿名lamba表达式:
        [&](void)
            ->bool
    {
        // 刷新日志记录器的指定日志级别的日志文件
        flush(LevelHelper::castFromInt(lIndex), nullptr);
        return false;
    }

    2、 void flush(Level level, base:: type:: fstream_t *fs);

    // 刷新日志记录器的指定日志级别的日志文件
    void Logger::flush(Level level, base::type::fstream_t *fs)
    {
        if (fs == nullptr && m_typedConfigurations->toFile(level))
        {
            // 获取日志级别对应的文件流
            fs = m_typedConfigurations->fileStream(level);
        }
        if (fs != nullptr)
        {
            // 文件存在则刷新文件
            fs->flush();
            // 重置日志级别对应的未刷新次数
            std::unordered_map<Level, unsigned int>::iterator iter = m_unflushedCount.find(level);
            if (iter != m_unflushedCount.end())
            {
                iter->second = 0;
            }
            // 检查日志级别对应的日志文件是否要进行日志回旋
            Helpers::validateFileRolling(this, level);
        }
    }

initUnflushedCount接口

    initUnflushedCount 主要用于初始化日志记录器的所有日志级别对应的日志文件的未刷新次数

    void Logger::initUnflushedCount(void)
    {
        m_unflushedCount.clear();
        base::type::EnumType lIndex = LevelHelper::kMinValid;
        LevelHelper::forEachLevel(&lIndex, [&](void) -> bool
                                  {
          m_unflushedCount.insert(std::make_pair(LevelHelper::castFromInt(lIndex), 0));
          return false; });
    }

    LevelHelper:: forEachLevel 的作用上面已经介绍过了,这里就不多说了。

el::base::MessageBuilder类

    用于支持各种类型的日志输出

成员变量

     Logger* m_logger; //日志记录器    
     const base::type::char_t* m_containerLogSeparator; //输出容器时,各个元素之间的分隔符。

成员函数

    主要是重载了各种类型的输出运算符,同时支持流操控符(如 std:: endl)

支持流操控符

    inline MessageBuilder &operator<<(std::ostream &(*OStreamMani)(std::ostream &))
    {
        m_logger->stream() << OStreamMani;
        return *this;
    }

支持内置类型

    // char
    ELPP_SIMPLE_LOG(char)
    // bool
    ELPP_SIMPLE_LOG(bool)
    // signed short
    ELPP_SIMPLE_LOG(signed short)
    // unsigned short
    ELPP_SIMPLE_LOG(unsigned short)
    // signed int
    ELPP_SIMPLE_LOG(signed int)
    // unsigned int
    ELPP_SIMPLE_LOG(unsigned int)
    // signed long
    ELPP_SIMPLE_LOG(signed long)
    // unsigned long
    ELPP_SIMPLE_LOG(unsigned long)
    // float
    ELPP_SIMPLE_LOG(float)
    // double
    ELPP_SIMPLE_LOG(double)
    // char*
    ELPP_SIMPLE_LOG(char *)
    // const char*
    ELPP_SIMPLE_LOG(const char *)
    // const void*
    ELPP_SIMPLE_LOG(const void *)
    // long double
    ELPP_SIMPLE_LOG(long double)

    ELPP_SIMPLE_LOG 宏,前面日志信息保存流程中已经介绍过了。

支持std::string 类型

    inline MessageBuilder &operator<<(const std::string &msg)
    {
        return operator<<(msg.c_str());
    }

支持std::wstring 类型

    inline MessageBuilder &operator<<(const std::wstring &msg)
    {
        return operator<<(msg.c_str());
    }

支持wchar_t*类型

    MessageBuilder &MessageBuilder::operator<<(const wchar_t *msg)
    {
        if (msg == nullptr)
        {
            m_logger->stream() << base::consts::kNullPointer;
            return *this;
        }
    #if defined(ELPP_UNICODE)
        m_logger->stream() << msg;
    #else
        // 非unicode时,将wchar*转为char*
        char *buff_ = base::utils::Str::wcharPtrToCharPtr(msg);
        m_logger->stream() << buff_;
        free(buff_);
    #endif
        if (ELPP->hasFlag(LoggingFlag::AutoSpacing))
        {
            m_logger->stream() << " ";
        }
        return *this;
    }

支持容器相关类型

    // 支持一个模板参数的容器
    #define ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(temp)                                               \
        template <typename T>                                                                       \
        inline MessageBuilder &operator<<(const temp<T> &template_inst)                             \
        {                                                                                           \
            return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \
        }
    
    // 支持2个模板参数的容器
    #define ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(temp)                                               \
        template <typename T1, typename T2>                                                         \
        inline MessageBuilder &operator<<(const temp<T1, T2> &template_inst)                        \
        {                                                                                           \
            return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \
        }
    
    // 支持3个模板参数的容器
    #define ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(temp)                                             \
        template <typename T1, typename T2, typename T3>                                            \
        inline MessageBuilder &operator<<(const temp<T1, T2, T3> &template_inst)                    \
        {                                                                                           \
            return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \
        }
    
    // 支持4个模板参数的容器
    #define ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(temp)                                              \
        template <typename T1, typename T2, typename T3, typename T4>                               \
        inline MessageBuilder &operator<<(const temp<T1, T2, T3, T4> &template_inst)                \
        {                                                                                           \
            return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \
        }
    
    // 支持5个模板参数的容器
    #define ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(temp)                                              \
        template <typename T1, typename T2, typename T3, typename T4, typename T5>                  \
        inline MessageBuilder &operator<<(const temp<T1, T2, T3, T4, T5> &template_inst)            \
        {                                                                                           \
            return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \
        }

    上面这几个宏内部都是委托给 writeIterator 这个成员函数模板来实现的:

    template <class Iterator>
    MessageBuilder &writeIterator(Iterator begin_, Iterator end_, std::size_t size_)
    {
        m_logger->stream() << ELPP_LITERAL("[");
        for (std::size_t i = 0; begin_ != end_ && i < base::consts::kMaxLogPerContainer; ++i, ++begin_)
        {
            operator<<(*begin_);
            m_logger->stream() << ((i < size_ - 1) ? m_containerLogSeparator : ELPP_LITERAL(""));
        }
        if (begin_ != end_)
        {
            m_logger->stream() << ELPP_LITERAL("...");
        }
        m_logger->stream() << ELPP_LITERAL("]");
        if (ELPP->hasFlag(LoggingFlag::AutoSpacing))
        {
            m_logger->stream() << " ";
        }
        return *this;
    }
    

    实现也不复杂,就是遍历容器对应的迭代器,依次保存在 m_logger 对应的 stream(字符串流对象)中。

    相关容器的宏使用:

    //STL容器
    ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::vector)
    ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::list)
    ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::deque)
    ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(std::set)
    ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(std::multiset)
    ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::map)
    ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::multimap)
    ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(std::unordered_map)
    ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(std::unordered_multimap)
    ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::unordered_set)
    ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::unordered_multiset)>>>     
    //boost容器
    ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::vector)
    ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::stable_vector)
    ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::list)
    ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::deque)
    ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(boost::container::map)
    ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(boost::container::flat_map)
    ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(boost::container::set)
    ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(boost::container::flat_set)>>>     
    //QT容器
    ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QList)
    ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QVector)
    ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QQueue)
    ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QSet)
    ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QLinkedList)
    ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QStack)>>>     
    //WXWIDGETS容器
    ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(wxVector)

    可以对照相关文档看这些容器为啥分别用的这些宏,主要是不同容器实际的模板参数个数的区别。
    对于其他的一些类型,el::base::MessageBuilder 提供了相应类型的输出运算符的重载,因为实现和上面类似,这里只是列出了声明:

    // std::pair
    template <class First, class Second>
    MessageBuilder &operator<<(const std::pair<First, Second> &pair_);
    // std::bitset
    template <std::size_t Size>
    MessageBuilder &operator<<(const std::bitset<Size> &bitset_);
    // std::array
    template <class T, std::size_t Size>
    inline MessageBuilder &operator<<(const std::array<T, Size> &array);
    // QString
    inline MessageBuilder &operator<<(const QString &msg);
    // QByteArray
    inline MessageBuilder &operator<<(const QByteArray &msg);
    // QStringRef
    inline MessageBuilder &operator<<(const QStringRef &msg);
    // qint64
    inline MessageBuilder &operator<<(qint64 msg);
    // quint64
    inline MessageBuilder &operator<<(quint64 msg);
    // QChar
    inline MessageBuilder &operator<<(QChar msg);
    // QLatin1String
    inline MessageBuilder &operator<<(const QLatin1String &msg);
    // QPair<First, Second>
    template <typename First, typename Second>
    MessageBuilder &operator<<(const QPair<First, Second> &pair_);
    // QMap<K, V>
    template <typename K, typename V>
    MessageBuilder &operator<<(const QMap<K, V> &map_);
    // QMultiMap<K, V>
    template <typename K, typename V>
    inline MessageBuilder &operator<<(const QMultiMap<K, V> &map_);
    // QHash<K, V>
    template <typename K, typename V>
    MessageBuilder &operator<<(const QHash<K, V> &hash_);
    // QMultiHash<K, V>
    template <typename K, typename V>
    inline MessageBuilder &operator<<(const QMultiHash<K, V> &multiHash_);

支持WXWIDGETS 相关类型

    #define ELPP_WX_PTR_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), *(*elem))
    #define ELPP_WX_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), (*elem))
    #define ELPP_WX_HASH_MAP_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), \
          ELPP_LITERAL("(") << elem->first << ELPP_LITERAL(", ") << elem->second << ELPP_LITERAL(")")

    MAKE_CONTAINERELPP_FRIENDLY 宏定义如下:

    #define MAKE_CONTAINERELPP_FRIENDLY(ContainerType, SizeMethod, ElementInstance)                                                                \
        el::base::type::ostream_t &operator<<(el::base::type::ostream_t &ss, const ContainerType &container)                                       \
        {                                                                                                                                          \
            const el::base::type::char_t *sep = ELPP->hasFlag(el::LoggingFlag::NewLineForContainer) ? ELPP_LITERAL("\n    ") : ELPP_LITERAL(", "); \
            ContainerType::const_iterator elem = container.begin();                                                                                \
            ContainerType::const_iterator endElem = container.end();                                                                               \
            std::size_t size_ = container.SizeMethod;                                                                                              \
            ss << ELPP_LITERAL("[");                                                                                                               \
            for (std::size_t i = 0; elem != endElem && i < el::base::consts::kMaxLogPerContainer; ++i, ++elem)                                     \
            {                                                                                                                                      \
                ss << ElementInstance;                                                                                                             \
                ss << ((i < size_ - 1) ? sep : ELPP_LITERAL(""));                                                                                  \
            }                                                                                                                                      \
            if (elem != endElem)                                                                                                                   \
            {                                                                                                                                      \
                ss << ELPP_LITERAL("...");                                                                                                         \
            }                                                                                                                                      \
            ss << ELPP_LITERAL("]");                                                                                                               \
            return ss;                                                                                                                             \
        }

    实现也不复杂,就是遍历容器对应的迭代器,依次保存在 ss 流中。
    之所以这里还需要 ElementInstance 这个宏参数,从上面的使用可以看到,WXWIDGETS 相关容器类型的元素值的获取方式是有区别的,有的是一个解引用,有的是两次解引用,还有的希望定制化输出对应元素的值,所以这里提供了出来一个宏参数,让使用者来决定如何针对容器元素定制化输出。

其他类型的支持

    template <class Class>
    ELPP_SIMPLE_LOG(const Class &)

    上面这个宏展开后为:

    template <class Class>
    MessageBuilder &operator<<(const Class &msg)
    {
        m_logger->stream() << msg; // 需要字符串流支持Class类型
        if (ELPP->hasFlag(LoggingFlag::AutoSpacing))
        {
            m_logger->stream() << " ";
        }
        return *this;
    }

el::base::DispatchAction枚举类型

    enum class DispatchAction : base::type::EnumType
    {
        None = 1,
        NormalLog = 2, // 用户日志
        SysLog = 4     // 系统 syslog 日志(需要系统支持 syslog 日志)
    };

CLOG 宏的实现到这里就介绍完了,下一篇我们继续介绍其他日志记录宏的实现。

posted @ 2022-11-26 21:49  节奏自由  阅读(78)  评论(0编辑  收藏  举报