高性能分布式网络服务器--日志模块

日志模块

日志模块主要用于格式化输出程序日志,方便后续从日志中定位程序运行过程中出现的问题。当然日志除了日志内容本身之外,还应该包括文件名、行号、时间戳、线程、协程号、日志级别等信息。在输出错误日志时,还应附加程序的函数调用栈信息,便于后续分析和排查问题

从设计上看,一个完整的日志模块应该具备以下几点基本功能

  • 日志级别。日志应该分为不同的级别,并且可以很方便的控制程序只输出大于某一级别的日志。比如:在调试和测试阶段可以设置低级别日志,尽可能多输出相关信息,方便开发人员定位问题;在程序发布之后设置一个较高级别的日志,以减少日志输出对性能产生的影响
  • 日志格式。可灵活配置每条日志应该携带哪些附加信息和输出格式
  • 日志类别。日志可以分类,同一程序可以同时有多个日志类别。这样可以很清晰的知道到当前日志属于哪一模块
  • 日志输出。可灵活配置不同模块的日志可输出到不同目的地,同时同一条日志也应该可被输出到多个目的地

GitHub

  • https://github.com/huxiaohei/tiger.git

实现

日志模块主要包含LogLevelLogEventLogEventWarpLogFormatterLogAppenderLoggerLoggerMgr类和用于定义配置格式的LoggerDefineAppenderDefine结构体,以及方便使用的宏

LogLevel

用于定义日志级别,以及日志级别与字符串的相互转换

LogEvent

日志事件用于记录日志现场,包括日志发生的日志级别、行号、文件名、线程号、线程名称、协程号、事件、日志器名称等

LogEventWarp

日志事件包装类,在日志现场构造并包装日志事件对象,在日志记录结束后,LogEventWrap析构时会使用日志事件中当时保存的日志器进行日志输出。包装类有利于后续使用宏来简化日志模块的使用

LogFormatter

日志格式器,用于格式化一个日志事件。由于日志事件包含了很多项(文件名、行号、时间戳、线程、协程号、日志级别等),但使用者并不希望每次都输出全部项,并且希望在日志中添加一个指定字符串。 因此日志格式器提供了模版字符串用于满足使用者这一需求。
日志格式器在构造的时候会先解析模版,获取模版所需对象的日志项并保存到日志器的一个属性中。在真的输出日志时,取出对应日志项实例组,将日志事件作为参数传入到每个日志项输出具体日志,日志项在根据自身类型决定输出具体类容

  • 模版字符串示例

    "%d{%Y-%m-%d %H:%M:%S} %t %N %F [%p] [%c] %f:%l %m%n";
    
  • 解析模版获取对应日志项

    void LogFormatter::init() {
        std::vector<std::tuple<std::string, std::string, int>> vec;
        std::string nstr;
        // ... 模版解析代码省略
        static std::map<std::string, std::function<FormatItem::ptr(const std::string &str)>> s_format_items = {
    #define XX(tag, Format)                                                            \
        {                                                                              \
    #tag, [](const std::string &fmt) { return std::make_shared<Format>(fmt); } \
        }
            XX(m, MessageFormatItem),     // m:消息
            XX(p, LevelFormatItem),       // p:日志级别
            XX(c, LoggerNameFormatItem),  // c:日志名称
            XX(t, ThreadIdFormatItem),    // t:线程id
            XX(n, NewLineFormatItem),     // n:换行
            XX(d, DateTimeFormatItem),    // d:时间
            XX(f, FileNameFormatItem),    // f:文件名
            XX(l, LineFormatItem),        // l:行号
            XX(T, TabFormatItem),         // T:Tab
            XX(F, CoIdFormatItem),        // F:协程id
            XX(N, ThreadNameFormatItem),  // N:线程名称
    #undef XX
        };
        for (auto &i : vec) {
            if (std::get<2>(i) == 0) {
                FormatItem::ptr items = std::make_shared<StringFormatItem>(std::get<0>(i));
                m_items.push_back(items);
            } else {
                auto it = s_format_items.find(std::get<0>(i));
                if (it == s_format_items.end()) {
                    continue;
                } else {
                    m_items.push_back(it->second(std::get<1>(i)));
                }
            }
        }
    }
    
  • 选取日志项输出

    std::string LogFormatter::format(std::shared_ptr<Logger> logger,
                                     LogLevel::Level level,
                                     LogEvent::ptr event) {
        std::stringstream ss;
        for (auto &i : m_items)
            i->format(logger, ss, level, event);
        return ss.str();
    }
    
    std::ostream &LogFormatter::format(std::shared_ptr<Logger> logger,
                                       std::ostream &ofs,
                                       LogLevel::Level level,
                                       LogEvent::ptr event) {
        for (auto &i : m_items)
            i->format(logger, ofs, level, event);
        return ofs;
    }
    

LogAppender

日志输出器类,用于将日志输出到对应的位置。它实际上是一个虚基类,具有一个纯虚方法virtual void log(std::shared_ptr<Logger> logger, LogLevel::Level level, LogEvent::ptr event) = 0;,不同类型的日志输出器通过重载此方法,将日志输送到不同目的地

tiger里面实现了StdOutLogAppenderFileLogAppender日志输出器,分别将日志输送到控制台和文件

Logger

日志器,用于输出日志。这个类是直接与用户进行交互的类,提供了void log(LogLevel::Level level, const LogEvent::ptr event)接口,用于传入日志级别和日志事件,将日志输出。日志器的实现包含了日子器名称、日志器级别、对应的日志输出器组等。在使用者调用log接口时,日志器先判断是否大于等于自身级别。如果大于等于日志器将日志事件分别送往日志输出器组,这也是就实现了同一个日志事情可以在多个地方输出

void Logger::log(LogLevel::Level level, const LogEvent::ptr event) {
    if (level < m_level) return;
    SpinLock::Lock lock(m_lock);
    auto self = shared_from_this();
    for (auto &it : m_appenders)
        it->log(self, level, event);
}

LogManager

日志器管理类,用于统一管理日志器,项目中所有日志器都应该由同一个日志管理器管理。因此使用者应该使用SingletonLoggerMgr类,此类可以理解为LogManager的单例实现。日志管理器提供添加、删除、获取日志器接口

宏和结构体

为了实现模块的灵活性,日志模块定义了对应的日志配置格式,可通过配置初始化日志器

log:
  - name: SYSTEM
    level: DEBUG
    formater: "%d{%Y-%m-%d %H:%M:%S} %t %N %F [%p] [%c] %f:%l %m%n"
    appenders:
      - type: FileLogAppender
        level: INFO
        file: ./system
        interval: 7200
      - type: StdOutLogAppender
        level: DEBUG
  - name: TEST
    level: DEBUG
    formater: "%d{%Y-%m-%d %H:%M:%S} %t %N %F [%p] [%c] %f:%l %m%n"
    appenders:
      - type: FileLogAppender
        level: DEBUG
        file: ./test
        interval: 7200
      - type: StdOutLogAppender
        level: DEBUG

为了实现模块的便捷性,tiger中为日志器定了宏,使日志输出变的非常轻松

TIGER_LOG_D(tiger::TEST_LOG) << "[test log]";
TIGER_LOG_D(tiger::SYSTEM_LOG) << "[test log]";
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虎小黑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值