如何更新Boost.Spirit日志解析器以兼容单/多数据条目日志格式(C++17)
如何更新Boost.Spirit日志解析器以兼容单/多数据条目日志格式(C++17)
我来帮你搞定这个Boost.Spirit解析器的升级需求!你的核心痛点是要让原来只能处理单条insert数据的解析规则,现在能兼容单条/多条数据混合的新日志格式,同时还要严格符合C++17标准对吧?咱们一步步来改:
问题核心分析
原来的解析规则tcl只硬编码匹配了一组id;position;hex三元组,现在需要把这组规则变成可重复匹配的结构,并且把每一组匹配到的结果都加入到Messages容器中。同时必须保持对旧格式的完全兼容——也就是当一行只有一组数据时,解析逻辑依然正常工作。
具体修改方案
我把修改的核心点拆成几个清晰的步骤,让代码更易读也更易维护:
- 抽离单条数据的解析规则:把原来嵌在
tcl里的单条Message解析逻辑,拆成独立的message规则,这样代码结构更清晰,也方便重复调用。 - 支持多组数据匹配:让
tcl规则在匹配到insert =后,能连续匹配一个或多个message条目(用+量词实现,确保至少有一组数据)。 - 调整语义动作批量插入结果:原来的逻辑是把单条
Messagepush到全局容器,现在要把tcl解析出的所有Message批量插入,用Boost.Phoenix的insert实现更高效的批量操作。 - 简化赋值逻辑:去掉原来的
deferred_fill函数,改用Phoenix的直接构造语义动作,让代码更简洁直观。
修改后的完整代码
#include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix.hpp> #include <vector> #include <string> #include <sstream> namespace structs { struct Message { unsigned id; double position; std::string hex; }; using Messages = std::vector<Message>; } namespace qi = boost::spirit::qi; namespace phoenix = boost::phoenix; namespace parser { class log { using It = boost::spirit::istream_iterator; public: log() { using namespace qi; using namespace structs; using phoenix::push_back; using phoenix::insert; using phoenix::begin; using phoenix::end; using phoenix::construct; // 单条Message的解析规则:匹配id;position;hex;格式 message = (uint_ >> ';' >> double_ >> ';' >> lexeme[+xdigit] >> ';') [_val = construct<Message>(_1, _2, _3)]; // 解析insert行:跳过前面的无关内容,匹配insert = 后的所有数据条目 tcl = omit[*~char_('>')] >> '>' >> "insert =" >> +message [insert(_val, end(_val), begin(_1), end(_1))]; // 忽略非insert行:匹配到换行符前的所有内容 ignore = *~char_("\r\n"); // 整体规则:每行要么是insert行(解析后插入结果),要么是忽略行 start = skip(blank)[(tcl | ignore) % eol]; } structs::Messages read(const std::string& file_content) { structs::Messages messages; std::stringstream in{ file_content }; in >> std::noskipws; // 不跳过空白字符,确保istream_iterator能正确处理换行 It begin(in), end; bool ok = qi::parse(begin, end, start, messages); // 额外检查:确保整个输入都被正确解析,避免残留未处理内容 if (ok && begin != end) { ok = false; } return ok ? messages : structs::Messages{}; } private: qi::rule<It, structs::Message(), qi::blank_type> message; qi::rule<It, structs::Messages(), qi::blank_type> tcl; qi::rule<It, qi::blank_type> ignore; qi::rule<It, structs::Messages(), qi::blank_type> start; }; } auto file1{ R"([2025-06-06 09:33:30.002155] - 0.000 s => begins to load XML file [2025-06-06 09:33:30.250151] - 0.000 s => Received -> id: 1 [2025-06-06 09:33:30.253154] - 0.000 s => End initial Mnemonics [2025-06-06 09:33:36.163154] - 0.020 s => insert = 1;250.9;A0127FC384DF400115406C800011806C031A0000; [2025-06-06 09:33:36.164151] - 0.020 s => Received -> id: 2 [2025-06-06 09:35:05.444160] - 89.360 s => insert = 7;1655.9;A0027FC384E240010C8092A041FF83FFF; [2025-06-06 09:33:36.164151] - 0.020 s => Received -> element_id: 1 connection_id: command: free [2025-06-06 09:35:54.648162] - 138.560 s => insert = 9;2258.9;A002054384E3C0010C8092A041; [2025-06-06 09:36:07.028155] - 150.940 s => Received -> Value: 1)" }; auto file2{ R"([2025-06-06 09:33:30.002155] - 0.000 s => begins to load XML file [2025-06-06 09:33:30.250151] - 0.000 s => Received -> id: 1 [2025-06-06 09:33:30.253154] - 0.000 s => End initial Mnemonics [2025-06-06 09:33:36.163154] - 0.020 s => insert = 1;250.9;A0127FC384DF400115406C800011806C031A0000;2;253.9;A0027FC384DF40010C8092A041FF83FFF;3;449.9;A1027FC38F361210BF080007DF; [2025-06-06 09:33:36.164151] - 0.020 s => Received -> id: 2 [2025-06-06 09:35:05.444160] - 89.360 s => insert = 7;1655.9;A0027FC384E240010C8092A041FF83FFF; [2025-06-06 09:33:36.164151] - 0.020 s => Received -> element_id: 1 connection_id: command: free [2025-06-06 09:35:54.648162] - 138.560 s => insert = 9;2258.9;A002054384E3C0010C8092A041;10;2261.9;A0127FC384E3C00115412C80008B1007F4; [2025-06-06 09:36:07.028155] - 150.940 s => Received -> Value: 1)" }; int main() { parser::log log_parser; auto messages1 = log_parser.read(file1); if (messages1.size() != 3) { return 1; // 旧格式解析失败 } auto messages2 = log_parser.read(file2); if (messages2.size() != 6) { return 1; // 新格式解析失败 } return 0; }
关键细节说明
- 单条数据规则
message:用lexeme[+xdigit]确保十六进制字符串不会被空白字符干扰(虽然你的日志里没有,但这是解析字符串的好习惯),末尾的';'严格匹配你日志里每条数据的结尾格式,如果后续出现末尾无分号的情况,可以改成-lit(';')(可选分号)。 - 多组数据匹配:
+message表示匹配一个或多个message条目,完美兼容旧格式(单条)和新格式(多条),不需要额外的分支判断。 - 批量插入结果:用
phoenix::insert把tcl解析出的所有Message一次性插入到全局容器,比循环push更高效,代码也更简洁。 - 完整输入检查:在
read函数里额外检查迭代器是否走到末尾,避免因部分解析失败导致的隐性错误,让解析结果更可靠。
这样修改后,你的解析器就能无缝处理新旧两种日志格式,完全符合C++17标准,代码结构也更清晰,后续维护起来更轻松!
内容来源于stack exchange
相关产品推荐
相关产品推荐

