概述
日志消息是由许多要素组成,而日志格式化的主要作用,即是对日志消息进行格式化,组织成自己指定好的字符串结构
总体架构
日志消息(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中
key | val |
nullptrt | abcd[ |
d | %H:%M:%S |
nullptr | ][ |
p | nullptr |
nullptr | ] |
T | nullptr |
m | nullptr |
n | nullptr |
简单工厂模式应用分析
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;
}