# 天眼/微步 JSON 解析异常排查入口 ## 目标 定位一条日志为什么进入 `syslog_non_normal_message` 并标记为“JSON解析异常”。 只分析当前设备绑定的规则: - 天眼设备重点看规则 `129 / skyeye_alarm_log`; - 微步设备看该设备实际绑定的规则,现场已见 `150 / weibu_TDP_alarmlog_JSON`、 `158 / weibujsomv2`、`159 / weibujson`; - `Threatbook[pid]: {...}` 属于微步/TDP 报文,不要拿规则129分析; - 设备与规则的绑定以 `dm_normalize_rule_device` 为准,不要模糊查询全表后把所有规则都拿来匹配。 ## 第一步:取得两个输入 ### 1. 解析前的完整原文 不要使用达梦非标表里带有以下标记的内容判断 JSON 是否合法: ```text [HB_XDR_TRUNCATED original_utf8_bytes=...] ``` 该内容已经在规则计算完成后按 2KB 限制截断,不是 Jackson 实际收到的报文。 使用 v5 脚本按设备采集解析前原文: ```bash chmod 700 upgrade-doc/v5/capture-device-raw-logs.sh upgrade-doc/v5/capture-device-raw-logs.sh \ <设备ID> 120 20 \ http://127.0.0.1:8289/xdrrule \ /data/haobang/raw-log-captures ``` 调试需要生成目录中的 `result.json` 和 `raw/*.log`。如果接口不可用,再从原始 InfluxDB 或 `syslog-serve` 接收日志按 `device_id + receive_time/event_id` 导出完整 `message`。 ### 2. 当前设备绑定的完整规则 `rule_content` 最长约一万多字符,在达梦 `disql` 中设置 20000 字符并 spool 到文件: ```sql SET LONG 20000 SET LONGCHUNKSIZE 20000 SET LINESIZE 32767 SET PAGESIZE 0 SET TRIMSPOOL ON SPOOL /tmp/device-normalize-rules.txt SELECT b.device_id, r.id AS rule_id, r.name, r.display_name, r.data_type, DBMS_LOB.GETLENGTH(r.rule_content) AS rule_content_length, DBMS_LOB.SUBSTR(r.rule_content, 20000, 1) AS rule_content FROM dm_normalize_rule_device b JOIN dm_normalize_rule r ON r.id = b.normalize_rule_id WHERE b.device_id = <设备ID> AND r.del_flag = '0' ORDER BY r.id; SPOOL OFF ``` 若长度超过 20000,再取下一段: ```sql DBMS_LOB.SUBSTR(rule_content, 20000, 20001) ``` ## 第二步:在代码中拿到 Jackson 的真实输入 当前主链路: ```text SysLogProcessor.prepareNormalization() -> LogNormalProcessor.prepare() -> LogNormalProcessor.collectCandidates() -> CompiledRule.isApplicableTo() -> CompiledRule.parse() -> logNormalData.ParserMessageJsonType() -> JsonParser.extractJsonFromLogMessage()(必要时) -> NestedJsonParserUtil.safeParseJson() -> Jackson ``` 建议断点: | 位置 | 观察内容 | | --- | --- | | `LogNormalProcessor.java:516` | 当前设备加载了哪些规则,每条规则是否进入候选解析 | | `CompiledRule.java:72` | 当前 `ruleId`、原始 `message` 和 cropper 配置 | | `CompiledRule.java:94` | cropper 结果是否需要再次截取 `{...}` | | `CompiledRule.java:99` | 变量 `cropped`,即 Compiled 链路交给 Jackson 的完整字符串 | | `CompiledRule.java:124` | `head_key` 是否真的能标识这类报文 | | `NestedJsonParserUtil.java:41` | Jackson 的首次解析结果及准确行列错误 | | `LogNormalProcessor.java:1014` | 关闭 Compiled 优化时的 legacy 入口,观察 `strJson` | 相关源码: ```text syslog-consumer-rule/src/main/java/com/Modules/NormalData/LogNormalProcessor.java syslog-consumer-rule/src/main/java/com/Modules/NormalData/compiled/CompiledRule.java syslog-consumer-rule/src/main/java/com/Modules/NormalData/logNormalData.java syslog-consumer-rule/src/main/java/com/common/util/JsonParser.java syslog-consumer-rule/src/main/java/com/common/util/NestedJsonParserUtil.java syslog-consumer-rule/src/main/java/com/common/service/RawLaneFieldLimiter.java ``` 对每条失败样本保存以下信息即可: ```text deviceId ruleId / ruleName 完整原文 message cropper_params 截取结果 cropped(或 legacy 的 strJson) Jackson 原始错误及行列号 ``` 不要为了调试在生产 `INFO` 日志中持续打印完整原文;使用采集文件、断点或只针对一条脱敏样本的测试。 ## 第三步:根据真实输入分类 | 现象 | 结论 | 处理位置 | | --- | --- | --- | | `cropped` 仍含 Syslog 头或天眼分隔前缀 | 截取未完成 | `cropper_params` 或 JSON 对象边界截取 | | `cropped` 缺少开头 `{`、结尾 `}` 或被截在中间 | cropper 范围错误 | 优先修当前规则 cropper | | 报文不含规则的格式特征,却进入该规则 | 候选规则归属错误 | `isApplicableTo()`/规则特征配置 | | `cropped` 是完整外层 JSON,但 `req_body` 等字段内有未转义双引号、反斜杠或真实换行 | 设备报文/嵌套字段转义错误 | 优先让设备修;必要时做设备/规则专用预处理 | | 完整原文在进入规则前已经不完整 | 上游提前截断 | 查 `syslog-serve`、Kafka 和原始存储链路 | | 只有达梦非标字段被截断 | 与解析失败无关 | 不修改解析代码 | `JsonParser.extractJsonFromLogMessage()` 的默认行为是取第一个 `{` 到最后一个 `}`。配置了 cropper 时, 先执行 `ParserMessageJsonType()`;如果结果不是完整 `{...}`,当前 Compiled JSON 路径才会再次执行对象边界截取。 ## 天眼与微步的现有复现入口 测试文件: ```text syslog-consumer-rule/src/test/java/com/Modules/NormalData/LogNormalProcessorRuleSelectionTest.java ``` 直接参考两个用例: - `threatbookUsesTdpRuleAfterFixedOffsetHoneypotRulesAreRejected`:微步 Threatbook 报文应选择规则150, 不能被158/159的固定偏移规则提前判为非标; - `skyeyeCropperPrefixIsNarrowedToOuterJsonBeforeJackson`:天眼规则129截取后,Jackson 输入必须是完整外层 JSON。 把现场完整原文脱敏后增加为 fixture,并使用现场规则的真实 `cropper_params`。至少断言: ```text 选中的 normalize_rule_id 一个关键字段的映射值 未生成“JSON解析异常”非标记录 ``` 使用 JDK 8 执行: ```bash mvn -Dtest=LogNormalProcessorRuleSelectionTest test ``` ## 必看的历史修改 ```bash git show a70707c72969c7979aef9c0221fd55360c365227 git show a70707c^:syslog-consumer-rule/src/main/java/com/common/util/NestedJsonParserUtil.java git show c6e3505 ``` - `a70707c...` 删除了首次失败后的全局坏 JSON 猜测修复和第二次 Jackson 解析; - 旧版会依次尝试修复未转义引号、单引号、反斜杠、缺失引号、数值、布尔和 null; - `c6e3505` 修改了候选规则失败处理及 JSON 对象边界截取,避免前一条不适用规则失败后立即转非标。 判断问题是否由删除旧修复引起,必须对同一份完整原文记录四项结果: ```text 当前首次解析结果 旧版首次解析结果 旧版修复后的字符串(并指出实际生效步骤) 旧版第二次解析结果 ``` 没有这四项证据,不要恢复旧版全局修复。全局恢复会给每条坏 JSON 增加多次扫描、正则替换、字符串分配和 第二次 Jackson 解析。若确实需要兼容设备缺陷,优先限定到指定设备及指定规则,最后才考虑通用解析器。