You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何更新Boost.Spirit日志解析器以兼容单/多数据条目日志格式(C++17)

如何更新Boost.Spirit日志解析器以兼容单/多数据条目日志格式(C++17)

我来帮你搞定这个Boost.Spirit解析器的升级需求!你的核心痛点是要让原来只能处理单条insert数据的解析规则,现在能兼容单条/多条数据混合的新日志格式,同时还要严格符合C++17标准对吧?咱们一步步来改:

问题核心分析

原来的解析规则tcl只硬编码匹配了一组id;position;hex三元组,现在需要把这组规则变成可重复匹配的结构,并且把每一组匹配到的结果都加入到Messages容器中。同时必须保持对旧格式的完全兼容——也就是当一行只有一组数据时,解析逻辑依然正常工作。

具体修改方案

我把修改的核心点拆成几个清晰的步骤,让代码更易读也更易维护:

  1. 抽离单条数据的解析规则:把原来嵌在tcl里的单条Message解析逻辑,拆成独立的message规则,这样代码结构更清晰,也方便重复调用。
  2. 支持多组数据匹配:让tcl规则在匹配到insert =后,能连续匹配一个或多个message条目(用+量词实现,确保至少有一组数据)。
  3. 调整语义动作批量插入结果:原来的逻辑是把单条Messagepush到全局容器,现在要把tcl解析出的所有Message批量插入,用Boost.Phoenix的insert实现更高效的批量操作。
  4. 简化赋值逻辑:去掉原来的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 07:53:07