高性能日志系统 日志格式化输出逻辑

概述

日志消息是由许多要素组成,而日志格式化的主要作用,即是对日志消息进行格式化,组织成自己指定好的字符串结构

总体架构

  • 日志消息(LogMsg)

    • 用于存储各种日志信息,例如存储日志的级别、时间、行号等信息

  • 格式化子项(FormerItem)

    • 用于输出每一条单个的日志消息

    • 该出设计成继承方法,首先实现一个抽象基类,该基类实现所有格式化子项的公共接口;然后子类继承该抽象类,实现其具体化的格式化逻辑,比如输出时间、级别等信息

  • 格式化器(Formatter)

    • 作用:首先根据用户指定的pattern 将其解析成字符串(key:value)数组存储,然后根据解析的结果,生成具体的FormatItem实例对象,然后将这些对象放入到Items中存储,从而实现不同场景下生成不同日志信息的目的。

    • 将解析和打印日志信息放在一起,目的是为了一个接口实现指定日志打印。

  • 该模块架构总结:采用工厂模式,创建具体格式化子项,可以根据用户提供的不同pattern生成不同的格式化子项。借助该模式,实现快速拓展格式化子项,因为只需要在抽象类下添加新的子类即可。

具体实现

格式化子项的定义与实现

  • 基础类:FormatItem
    • 共享指针管理创建的实例对象,父类指针指向子类对象,从而实现调用子类对象成员函数的目的
    • 定义纯虚函数 format,子类就是通过重写该函数,实现不同的格式化日志消息
  • 子类:具体实现不同日志内容
    • 子类重写父类的format函数,然后生成不同的日志信息,比如日志级别、格式化时间等

pattern解析逻辑

pattern解析逻辑

  • 初始状态

    • 初始化一些临时变量,format_key用于存储格式化字符(%d  %p)format_val用于存储子格式字符串(如时间格式),string_row用于存储非格式化字符
    • 使用一个向量arry来存储解析后的结果,也就是存储解析出来格式化指令和其信息(下文事例中key:value)
  • 遍历模式字符串

    • 遍历格式化字符串,逐字符处理
    • 如果遇到普通字符(非%,则将其加入string_row
    • 如果遇到%,需要进一步判断是格式化字符还是转义的%
      • 如果是%%,则将其视作单个%字符,追加存储在string_row中
  • 处理格式化字符

    • 如果遇到格式化字符先将前面的非格式化字符串(如果有)存入arry
    • 读取格式化字符,并检查是否有子格式字符串,检查后面是否存在{}(如时间格式中的{%Y-%m-%d})。
    • 将格式化字符和子格式字符串存入arry(详细理解结合下面表格解析)
  • 处理结束

    • 如果最后还有非格式化字符串未处理,将其存入arry
    • 遍历arry,创建相应的格式化项实例并存入_items向量中

【事例分析】abcd [%d{%H:%M:%S}] [ %p] %T%m%n   ---- 分析出格式化数组存储到arry,然后根据格式化数组构建格式化子项,最终添加到items中

keyval
nullptrtabcd[
d%H:%M:%S
nullptr][
pnullptr
nullptr]
Tnullptr
mnullptr
nnullptr

简单工厂模式应用分析

Formatter类就是一个工厂,其可以根据传入格式化指定的不同,创建不同的格式化子项(也及时FormatItem子类的实例),下面将具体分析实现。

工厂方法:根据解析的格式化字符,创建对象的格式化子项指针

FormatItem::ptr createItem(const std::string &fc, const std::string &subfmt) {
    if (fc == "m") return FormatItem::ptr(new MsgFormatItem(subfmt));
    if (fc == "p") return FormatItem::ptr(new LevelFormatItem(subfmt));
    if (fc == "c") return FormatItem::ptr(new NameFormatItem(subfmt));
    if (fc == "t") return FormatItem::ptr(new ThreadFormatItem(subfmt));
    if (fc == "n") return FormatItem::ptr(new NLineFormatItem(subfmt));
    if (fc == "d") return FormatItem::ptr(new TimeFormatItem(subfmt));
    if (fc == "f") return FormatItem::ptr(new CFileFormatItem(subfmt));   
    if (fc == "l") return FormatItem::ptr(new CLineFormatItem(subfmt));
    if (fc == "T") return FormatItem::ptr(new TabFormatItem(subfmt));
    return FormatItem::ptr(); 
}

工厂方法调用: 解析逻辑调用,即根据格式化字符解析成格式化子项后,然后将这些格式化子项存入到Items中,从而完成完成最终日志打印格式的目的 

bool parsePattern() {
    // ...  ...

    for (auto &it : arry) {
        if (std::get<2>(it) == 0) {
            FormatItem::ptr fi(new OtherFormatItem(std::get<0>(it)));
            _items.push_back(fi);
        } else {
            FormatItem::ptr fi = createItem(std::get<0>(it), std::get<1>(it));
            if (fi.get() == nullptr) {
                std::cout << "没有对应的格式化字符: %" <<  std::get<0>(it) << std::endl;
                return false;
            }
            _items.push_back(fi);
        }
    }
    // ... ...
}

单元功能测试

简单测试,基本功能验证

#include "formatter.hpp"
#include "message.hpp"
#include "level.hpp"
#include "util.hpp"
#include <iostream>

int main() {
    // 创建一个测试日志消息
    std::string logger_name = "TestLogger";
    std::string file_name = __FILE__;
    std::string log_payload = "This is a test log message.";
    size_t line_number = __LINE__;
    bitlog::LogLevel::value log_level = bitlog::LogLevel::value::INFO;  // 正确使用 enum class 枚举值

    // 初始化 LogMsg 对象
    bitlog::LogMsg msg(logger_name, file_name, line_number, std::move(log_payload), log_level);

    // 定义一个格式化模式
    std::string pattern = "[%d{%Y-%m-%d %H:%M:%S}][%t][%p][%c][%f:%l] %m%n";

    // 创建 Formatter 对象
    bitlog::Formatter::ptr formatter(new bitlog::Formatter(pattern));

    // 格式化并输出到标准输出流
    std::string formatted_log = formatter->format(msg);
    std::cout << formatted_log << std::endl;

    return 0;
}

复杂测试 :多线程环境

#include "formatter.hpp"
#include "message.hpp"
#include "level.hpp"
#include "util.hpp"
#include <iostream>
#include <thread>
#include <vector>

void log_test_message(std::string logger_name, bitlog::LogLevel::value log_level, std::string message, std::string pattern) {
    std::string file_name = __FILE__;
    size_t line_number = __LINE__;
    bitlog::LogMsg msg(logger_name, file_name, line_number, std::move(message), log_level);
    
    bitlog::Formatter::ptr formatter(new bitlog::Formatter(pattern));
    std::string formatted_log = formatter->format(msg);
    std::cout << formatted_log << std::endl;
}


int main() {
    // 不同的格式模式
    std::vector<std::string> patterns = {
        "[%d{%Y-%m-%d %H:%M:%S}][%t][%p][%c][%f:%l] %m%n",
        "[%d{%H:%M:%S}][%p] %m (%f:%l)%n",
        "[%t] %m%n"
    };

    // 不同的日志级别
    std::vector<bitlog::LogLevel::value> levels = {
        bitlog::LogLevel::value::DEBUG,
        bitlog::LogLevel::value::INFO,
        bitlog::LogLevel::value::WARN,
        bitlog::LogLevel::value::ERROR,
        bitlog::LogLevel::value::FATAL
    };

    // 日志消息
    std::vector<std::string> messages = {
        "这是一条调试信息。",
        "系统正在启动。",
        "检测到潜在问题。",
        "处理过程中发生错误。",
        "系统故障,需要立即采取措施!"
    };

    // 多线程记录日志
    std::vector<std::thread> threads;
    for (const auto &pattern : patterns) {
        for (size_t i = 0; i < levels.size(); ++i) {
            threads.emplace_back(log_test_message, "MultiThreadLogger", levels[i], messages[i], pattern);
        }
    }

    // 等待所有线程完成
    for (auto &thread : threads) {
        thread.join();
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值