easylogging++的那些事(四)源码分析(十五)浅谈easylogging++的设计理念

其他工具类三 中我们介绍了线程安全相关的一些类和接口,今天我们正式进入最后一个部分的分析: 设计理念篇。

总体设计 中我们从软件开发的流程的角度分析了 easylogging++的相关设计,今天我们结合前面的源码分析从编程范式的角度来观察其设计理念。

面向过程

    面向过程最明显的就是模块化,自定向下的设计思路。通过将一个复杂的问题逐步拆解为一个个更简单的子问题,然后逐步解决子问题,最终将这个复杂的问题给解决了。
    计算机世界的任何问题都可以通过引入中间层来解决,一个中间层如果解决不了,就再加一个中间层。
    在 总体设计 中 easylogging++的框架设计部分已经有了部分介绍。
    再比如 TypedConfigurations 类中 FORMAT 配置项的部分的解析工作相对复杂,又被拆分为 LogFormat 类来单独进行处理。
    另外,这其中设计到很多特定的操作,为了相关操作的便利性,内部实现了一系列相关的工具类:
    1. 文件操作类 File
    2. 字符串工具类 Str
    3. 操作系统相关工具类 OS
    4. 日期时间工具类 DateTime
    5. 解析命令行参数的工具类 CommandLineArgs
    6. 线程安全的一系列类: 通用互斥量类 Mutex、通用区间锁类 ScopedLock 和通用线程安全基类 ThreadSafe
    7. 操作 el::Level 的工具类 LevelHelper
    8. 操作 el::ConfigurationType 的工具类 ConfigurationTypeHelper
    这样每个类的功能更单一,避免设计大而全的类,也更加符合单一职责原则。

面向对象

    面向对象的特性(封装、抽象、继承、多态)天然让代码具有复用的特点, 让我们能够更轻松写出易复用、易扩展、易维护的代码。
    面向对象编程因为其具有丰富的特性,可以实现很多复杂的设计思路,是很多设计原则、设计模式等编码实现的基础。

Writer 类

    writer 类主要用于写日志,但 writer 类两种使用场景(日志记录宏以及类 printf 接口)中,都必须基于以下调用形式来使用:

el::base::Writer(xxx).construct(xxx) << "log";    

    Writer 类的实例只有在调用完 construct 接口后才能真正完成初始化的工作。
    从 Writer 类的应用场景来说,这是建造者模式的一种体现。

PErrorWriter 类

    PErrorWriter 类在日志内容里面添加 errno 错误信息,是对 Writer 类的增强。
    从 PErrorWriter 类的应用场景来说,这是装饰器模式的一种体现。

Helpers 和 Loggers 类

    HelpersLoggers 类对库使用的使用者而言提供了一系列更易用的接口。
    从 PErrorWriter 类的应用场景来说,这是门面模式的一种体现。

泛型编程

    模板将类型泛型化,在基本面向对象的基础上更将代码复用带到了一个全新的高度,通过类型参数化,同一份代码可以支持不同的类型。
    如果在加上继承的特性,代码复用将会更加灵活。

IterableContainer 抽象类模板, IterablePriorityQueue 子类模板, IterableQueue 子类模板和 IterableStack 子类模板

/// @brief Workarounds to write some STL logs
///
/// @detail There is workaround needed to loop through some stl containers. In order to do that, we need iterable containers
/// of same type and provide iterator interface and pass it on to writeIterator().
/// Remember, this is passed by value in constructor so that we dont change original containers.
/// This operation is as expensive as Big-O(std::min(class_.size(), base::consts::kMaxLogPerContainer))
namespace workarounds
{
    /// @brief Abstract IterableContainer template that provides interface for iterable classes of type T
    // 迭代特定类型的抽象类,提供begin()和end()接口用于迭代,用于el::base::MessageBuilder的writeIterator接口
    template <typename T, typename Container>
    class IterableContainer
    {
    public:
        typedef typename Container::iterator iterator;
        typedef typename Container::const_iterator const_iterator;
        IterableContainer(void) {}
        virtual ~IterableContainer(void) {}
        iterator begin(void)
        {
            return getContainer().begin();
        }
        iterator end(void)
        {
            return getContainer().end();
        }

    private:
        // 派生类需要实现getContainer接口,提供底层真正的容器类型
        virtual Container &getContainer(void) = 0;
    };

    /// @brief Implements IterableContainer and provides iterable std::priority_queue class
    // Container为std::vector<T>
    template <typename T, typename Container = std::vector<T>, typename Comparator = std::less<typename Container::value_type>>
    class IterablePriorityQueue : public IterableContainer<T, Container>,
                                  public std::priority_queue<T, Container, Comparator>
    {
    public:
        IterablePriorityQueue(std::priority_queue<T, Container, Comparator> queue_)
        {
            std::size_t count_ = 0;
            while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty())
            {
                this->push(queue_.top());
                queue_.pop();
            }
        }

    private:
        inline Container &getContainer(void)
        {
            return this->c;
        }
    };

    /// @brief Implements IterableContainer and provides iterable std::queue class
    // Container为std::deque<T>,而std::deque<T>默认的底层容器为std::vector<T>
    template <typename T, typename Container = std::deque<T>>
    class IterableQueue : public IterableContainer<T, Container>, public std::queue<T, Container>
    {
    public:
        IterableQueue(std::queue<T, Container> queue_)
        {
            std::size_t count_ = 0;
            while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty())
            {
                this->push(queue_.front());
                queue_.pop();
            }
        }

    private:
        inline Container &getContainer(void)
        {
            return this->c;
        }
    };
    /// @brief Implements IterableContainer and provides iterable std::stack class
    // Container为std::deque<T>,而std::deque<T>默认的底层容器为std::vector<T>
    template <typename T, typename Container = std::deque<T>>
    class IterableStack : public IterableContainer<T, Container>, public std::stack<T, Container>
    {
    public:
        IterableStack(std::stack<T, Container> stack_)
        {
            std::size_t count_ = 0;
            while (++count_ < base::consts::kMaxLogPerContainer && !stack_.empty())
            {
                this->push(stack_.top());
                stack_.pop();
            }
        }

    private:
        inline Container &getContainer(void)
        {
            return this->c;
        }
    };
} // namespace workarounds

    相关模板子类主要用于 el::base::MessageBuilderwriteIterator 接口,可以让 easylogging++对于 STL 的容器相关类型得到相应支持。
    IterableContainer 抽象类模板实现了 beginend 接口,用于迭代。
    通过将容器类型作为参数让 easylogging++适配具有 beginend 接口的容器类型。
    从这些类的应用场景来说,这是迭代器模式适配器模式的一种体现。

    上面的代码本身不复杂,这里就不作过多解释了。

AbstractRegistry 抽象类模板,Registry 模板子类以及 RegistryWithPred 模板子类


/// @brief Abstract registry (aka repository) that provides basic interface for pointer repository specified by T_Ptr type.
///
/// @detail Most of the functions are virtual final methods but anything implementing this abstract class should implement
/// unregisterAll() and deepCopy(const AbstractRegistry<T_Ptr, Container>&) and write registerNew() method according to container
/// and few more methods; get() to find element, unregister() to unregister single entry.
/// Please note that this is thread-unsafe and should also implement thread-safety mechanisms in implementation.
template <typename T_Ptr, typename Container>
class AbstractRegistry : public base::threading::ThreadSafe
{
public:
    typedef typename Container::iterator iterator;
    typedef typename Container::const_iterator const_iterator;

    /// @brief Default constructor
    AbstractRegistry(void) {}

    /// @brief Move constructor that is useful for base classes
    AbstractRegistry(AbstractRegistry &&sr)
    {
        if (this == &sr)
        {
            return;
        }
        unregisterAll();
        m_list = std::move(sr.m_list);
    }

    bool operator==(const AbstractRegistry<T_Ptr, Container> &other)
    {
        if (size() != other.size())
        {
            return false;
        }
        for (std::size_t i = 0; i < m_list.size(); ++i)
        {
            if (m_list.at(i) != other.m_list.at(i))
            {
                return false;
            }
        }
        return true;
    }

    bool operator!=(const AbstractRegistry<T_Ptr, Container> &other)
    {
        if (size() != other.size())
        {
            return true;
        }
        for (std::size_t i = 0; i < m_list.size(); ++i)
        {
            if (m_list.at(i) != other.m_list.at(i))
            {
                return true;
            }
        }
        return false;
    }

    /// @brief Assignment move operator
    AbstractRegistry &operator=(AbstractRegistry &&sr)
    {
        if (this == &sr)
        {
            return *this;
        }
        unregisterAll();
        m_list = std::move(sr.m_list);
        return *this;
    }

    virtual ~AbstractRegistry(void)
    {
    }

    /// @return Iterator pointer from start of repository
    virtual inline iterator begin(void) ELPP_FINAL
    {
        return m_list.begin();
    }

    /// @return Iterator pointer from end of repository
    virtual inline iterator end(void) ELPP_FINAL
    {
        return m_list.end();
    }

    /// @return Constant iterator pointer from start of repository
    virtual inline const_iterator cbegin(void) const ELPP_FINAL
    {
        return m_list.cbegin();
    }

    /// @return End of repository
    virtual inline const_iterator cend(void) const ELPP_FINAL
    {
        return m_list.cend();
    }

    /// @return Whether or not repository is empty
    virtual inline bool empty(void) const ELPP_FINAL
    {
        return m_list.empty();
    }

    /// @return Size of repository
    virtual inline std::size_t size(void) const ELPP_FINAL
    {
        return m_list.size();
    }

    /// @brief Returns underlying container by reference
    virtual inline Container &list(void) ELPP_FINAL
    {
        return m_list;
    }

    /// @brief Returns underlying container by constant reference.
    virtual inline const Container &list(void) const ELPP_FINAL
    {
        return m_list;
    }

    /// @brief Unregisters all the pointers from current repository.
    virtual void unregisterAll(void) = 0;

protected:
    virtual void deepCopy(const AbstractRegistry<T_Ptr, Container> &) = 0;
    void reinitDeepCopy(const AbstractRegistry<T_Ptr, Container> &sr)
    {
        unregisterAll();
        deepCopy(sr);
    }

private:
    Container m_list;
};

/// @brief A pointer registry mechanism to manage memory and provide search functionalities. (non-predicate version)
///
/// @detail NOTE: This is thread-unsafe implementation (although it contains lock function, it does not use these functions)
///         of AbstractRegistry<T_Ptr, Container>. Any implementation of this class should be
///         explicitly (by using lock functions)
template <typename T_Ptr, typename T_Key = const char *>
class Registry : public AbstractRegistry<T_Ptr, std::unordered_map<T_Key, T_Ptr *>>
{
public:
    typedef typename Registry<T_Ptr, T_Key>::iterator iterator;
    typedef typename Registry<T_Ptr, T_Key>::const_iterator const_iterator;

    Registry(void) {}

    /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor.
    Registry(const Registry &sr) : AbstractRegistry<T_Ptr, std::vector<T_Ptr *>>()
    {
        if (this == &sr)
        {
            return;
        }
        this->reinitDeepCopy(sr);
    }

    /// @brief Assignment operator that unregisters all the existing registries and deeply copies each of repo element
    /// @see unregisterAll()
    /// @see deepCopy(const AbstractRegistry&)
    Registry &operator=(const Registry &sr)
    {
        if (this == &sr)
        {
            return *this;
        }
        this->reinitDeepCopy(sr);
        return *this;
    }

    virtual ~Registry(void)
    {
        unregisterAll();
    }

protected:
    virtual void unregisterAll(void) ELPP_FINAL
    {
        if (!this->empty())
        {
            for (auto &&curr : this->list())
            {
                base::utils::safeDelete(curr.second);
            }
            this->list().clear();
        }
    }

    /// @brief Registers new registry to repository.
    virtual void registerNew(const T_Key &uniqKey, T_Ptr *ptr) ELPP_FINAL
    {
        unregister(uniqKey);
        this->list().insert(std::make_pair(uniqKey, ptr));
    }

    /// @brief Unregisters single entry mapped to specified unique key
    void unregister(const T_Key &uniqKey)
    {
        T_Ptr *existing = get(uniqKey);
        if (existing != nullptr)
        {
            this->list().erase(uniqKey);
            base::utils::safeDelete(existing);
        }
    }

    /// @brief Gets pointer from repository. If none found, nullptr is returned.
    T_Ptr *get(const T_Key &uniqKey)
    {
        iterator it = this->list().find(uniqKey);
        return it == this->list().end()
                   ? nullptr
                   : it->second;
    }

private:
    virtual void deepCopy(const AbstractRegistry<T_Ptr, std::unordered_map<T_Key, T_Ptr *>> &sr) ELPP_FINAL
    {
        for (const_iterator it = sr.cbegin(); it != sr.cend(); ++it)
        {
            registerNew(it->first, new T_Ptr(*it->second));
        }
    }
};

/// @brief A pointer registry mechanism to manage memory and provide search functionalities. (predicate version)
///
/// @detail NOTE: This is thread-unsafe implementation of AbstractRegistry<T_Ptr, Container>. Any implementation of this class
/// should be made thread-safe explicitly
template <typename T_Ptr, typename Pred>
class RegistryWithPred : public AbstractRegistry<T_Ptr, std::vector<T_Ptr *>>
{
public:
    typedef typename RegistryWithPred<T_Ptr, Pred>::iterator iterator;
    typedef typename RegistryWithPred<T_Ptr, Pred>::const_iterator const_iterator;

    RegistryWithPred(void)
    {
    }

    virtual ~RegistryWithPred(void)
    {
        unregisterAll();
    }

    /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor.
    RegistryWithPred(const RegistryWithPred &sr) : AbstractRegistry<T_Ptr, std::vector<T_Ptr *>>()
    {
        if (this == &sr)
        {
            return;
        }
        this->reinitDeepCopy(sr);
    }

    /// @brief Assignment operator that unregisters all the existing registries and deeply copies each of repo element
    /// @see unregisterAll()
    /// @see deepCopy(const AbstractRegistry&)
    RegistryWithPred &operator=(const RegistryWithPred &sr)
    {
        if (this == &sr)
        {
            return *this;
        }
        this->reinitDeepCopy(sr);
        return *this;
    }

    friend base::type::ostream_t &operator<<(base::type::ostream_t &os, const RegistryWithPred &sr)
    {
        for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it)
        {
            os << ELPP_LITERAL("    ") << **it << ELPP_LITERAL("\n");
        }
        return os;
    }

protected:
    virtual void unregisterAll(void) ELPP_FINAL
    {
        if (!this->empty())
        {
            for (auto &&curr : this->list())
            {
                base::utils::safeDelete(curr);
            }
            this->list().clear();
        }
    }

    virtual void unregister(T_Ptr *&ptr) ELPP_FINAL
    {
        if (ptr)
        {
            iterator iter = this->begin();
            for (; iter != this->end(); ++iter)
            {
                if (ptr == *iter)
                {
                    break;
                }
            }
            if (iter != this->end() && *iter != nullptr)
            {
                this->list().erase(iter);
                base::utils::safeDelete(*iter);
            }
        }
    }

    virtual inline void registerNew(T_Ptr *ptr) ELPP_FINAL
    {
        this->list().push_back(ptr);
    }

    /// @brief Gets pointer from repository with specified arguments. Arguments are passed to predicate
    /// in order to validate pointer.
    template <typename T, typename T2>
    T_Ptr *get(const T &arg1, const T2 arg2)
    {
        iterator iter = std::find_if(this->list().begin(), this->list().end(), Pred(arg1, arg2));
        if (iter != this->list().end() && *iter != nullptr)
        {
            return *iter;
        }
        return nullptr;
    }

private:
    virtual void deepCopy(const AbstractRegistry<T_Ptr, std::vector<T_Ptr *>> &sr)
    {
        for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it)
        {
            registerNew(new T_Ptr(**it));
        }
    }
};

    AbstractRegistry 抽象类模板基于底层容器实现了容器相关的一系列方法:
    1. 用于迭代的 beginendcbegincend
    2. 判断容器是否为空的 empty
    3. 获取容器大小的 size
    4. 获取底层容器的 list

    而将与容器类型相关的差异性的操作留给子类去实现:
    子类需要实现 unregisterAlldeepCopy 方法。
    由于容器类型不同,添加和获取元素的方法不一定一致,故这些接口没有成为纯虚方法,直接让子类自己去实现。

    子类实现了以下方法:
    1. registerNew:添加元素
    2. unregister:删除元素
    3. get:获取元素

    从这些类的应用场景来说,这是模板模式的一种体现。

    基于模板参数类型 Container(底层容器类型)定义了类的迭代器别名 iteratorconst_iterator

typedef typename Container::iterator iterator;
typedef typename Container::const_iterator const_iterator;

    Registry(非谓词版本) 公有继承 AbstractRegistry <T_Ptr, std::unordered_map<T_Key, T_Ptr*> >
    底层容器为 std::unordered_map <T_Key, T_Ptr*>
    基于 AbstractRegistryiterator 别名定义了自身的迭代器类型别名 iteratorconst_iterator

// 实际iterator就是std::unordered_map<T_Key, T_Ptr*>::iterator
typedef typename Registry<T_Ptr, T_Key>::iterator iterator;
// 实际const_iterator就是std::unordered_map<T_Key, T_Ptr*>::iconst_iterator
typedef typename Registry<T_Ptr, T_Key>::const_iterator const_iterator;

    这个类非线程安全,原因:没有使用继承来的 base::threading::ThreadSafe 当中的锁相关的接口。
    如果想使这个类保证线程安全,必须在这个类的基础上使用组合或者继承方式,将相关需要加锁的接口包装成已加锁。

    RegistryWithPred(谓词版本) 公有继承 AbstractRegistry <T_Ptr, std::vector<T_Ptr*> >
    底层容器为 std:: vector <T_Ptr*>
    基于 AbstractRegistryiterator 别名定义了自身的迭代器类型别名 iteratorconst_iterator

typedef typename RegistryWithPred<T_Ptr, Pred>::iterator iterator;(实际iterator就是std::vector<T_Ptr*>::iterator)
typedef typename RegistryWithPred<T_Ptr, Pred>::const_iterator const_iterator;(实际const_iterator就是std::vector<T_Ptr*>::const_iterator)  

    这个类也非线程安全,原因:也是没有使用继承来的 base::threading::ThreadSafe 当中的锁相关的接口。
    如果想使用这个类保证线程安全,必须在这个类的基础上使用组合或者继承方式,将相关需要加锁的接口包装成已加锁。
    谓词主要用于 get 接口。

    上面三个类的代码本身不复杂,这里就不作过多解释了。

函数式编程

    一种越来越流行的编程范式,适当的使用能够使代码更简洁可读,写出更灵活、紧凑、优雅的代码。
    lambda 表达式是一个变量,所以,我们就可以 "按需分配",随时随地在调用点 "就地" 定义函数,限制它的作用域和生命周期,实现函数的局部化。
    如下面接口当中的 conditionalAddFlag 这个 lambda 表达式,我们仅仅只在当前这个函数内部使用:

/// @brief Updates format to be used while logging.
/// @param userFormat User provided format
void LogFormat::parseFromFormat(const base::type::string_t &userFormat)
{
    // We make copy because we will be changing the format
    // i.e, removing user provided date format from original format
    // and then storing it.
    base::type::string_t formatCopy = userFormat;
    m_flags = 0x0;
    auto conditionalAddFlag = [&](const base::type::char_t *specifier, base::FormatFlags flag)
    {
        std::size_t foundAt = base::type::string_t::npos;
        while ((foundAt = formatCopy.find(specifier, foundAt + 1)) != base::type::string_t::npos)
        {
            if (foundAt > 0 && formatCopy[foundAt - 1] == base::consts::kFormatSpecifierChar)
            {
                if (hasFlag(flag))
                {
                    // If we already have flag we remove the escape chars so that '%%' is turned to '%'
                    // even after specifier resolution - this is because we only replaceFirst specifier
                    formatCopy.erase(foundAt - 1, 1);
                    ++foundAt;
                }
            }
            else
            {
                if (!hasFlag(flag))
                    addFlag(flag);
            }
        }
    };
    conditionalAddFlag(base::consts::kAppNameFormatSpecifier, base::FormatFlags::AppName);
    conditionalAddFlag(base::consts::kSeverityLevelFormatSpecifier, base::FormatFlags::Level);
    conditionalAddFlag(base::consts::kSeverityLevelShortFormatSpecifier, base::FormatFlags::LevelShort);
    conditionalAddFlag(base::consts::kLoggerIdFormatSpecifier, base::FormatFlags::LoggerId);
    conditionalAddFlag(base::consts::kThreadIdFormatSpecifier, base::FormatFlags::ThreadId);
    conditionalAddFlag(base::consts::kLogFileFormatSpecifier, base::FormatFlags::File);
    conditionalAddFlag(base::consts::kLogFileBaseFormatSpecifier, base::FormatFlags::FileBase);
    conditionalAddFlag(base::consts::kLogLineFormatSpecifier, base::FormatFlags::Line);
    conditionalAddFlag(base::consts::kLogLocationFormatSpecifier, base::FormatFlags::Location);
    conditionalAddFlag(base::consts::kLogFunctionFormatSpecifier, base::FormatFlags::Function);
    conditionalAddFlag(base::consts::kCurrentUserFormatSpecifier, base::FormatFlags::User);
    conditionalAddFlag(base::consts::kCurrentHostFormatSpecifier, base::FormatFlags::Host);
    conditionalAddFlag(base::consts::kMessageFormatSpecifier, base::FormatFlags::LogMessage);
    conditionalAddFlag(base::consts::kVerboseLevelFormatSpecifier, base::FormatFlags::VerboseLevel);
    // For date/time we need to extract user's date format first
    std::size_t dateIndex = std::string::npos;
    if ((dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier)) != std::string::npos)
    {
        while (dateIndex != std::string::npos && dateIndex > 0 && formatCopy[dateIndex - 1] == base::consts::kFormatSpecifierChar)
        {
            dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier, dateIndex + 1);
        }
        if (dateIndex != std::string::npos)
        {
            addFlag(base::FormatFlags::DateTime);
            updateDateFormat(dateIndex, formatCopy);
        }
    }
    m_format = formatCopy;
    updateFormatSpec();
}

至此,easylogging++设计理念的内容就介绍到这里,同时这也是 easylogging++系列文章的最后一篇。

第一次写这种系列的源码分析文章,难免有所不足,算是抛砖引玉了。
欢迎大家在留言区提出建议,我们一起共同成长!

posted @ 2022-12-10 01:01  节奏自由  阅读(82)  评论(0编辑  收藏  举报