Files
ai-security-xdr/haobang-security-dm/upgrade-doc/v5/02-json-format-error-debug-guide.md
T
王绍宏 faa9ffe2d9 feat(syslog): 优化WAL配置并在入站阶段丢弃无效设备日志
- 默认在进入WAL前丢弃未注册、重复或未分配到当前探针的Syslog日志
- 使用后台设备内存快照,避免Netty网络线程访问Redis或达梦
- 增加Terminal审计开关,允许恢复原有逐条审计文件模式
- 历史WAL中的无效设备记录直接推进checkpoint
- 增加无效设备丢弃、快照状态及刷新失败监控指标
- 生产WAL队列调整为20000条
- WAL待写预算和分段大小调整为2GiB
- 同步生产配置、Docker Compose及升级文档
- 增加无效设备前置过滤和Terminal开关测试

chore(syslog): 统一生产环境 WAL 挂载目录

- 将生产环境 WAL 默认目录调整为 /app/logs/syslog-wal
- Docker Compose 显式设置 SYSLOG_WAL_DIRECTORY
- WAL 与应用日志共用 /app/logs 宿主机挂载
- 移除旧的 /data/syslog-archive 独立挂载
- 宿主机 WAL 路径统一为 /home/syslog/logs/syslog-wal
2026-09-15 22:10:46 +08:00

7.0 KiB
Raw Blame History

天眼/微步 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 是否合法:

[HB_XDR_TRUNCATED original_utf8_bytes=...]

该内容已经在规则计算完成后按 2KB 限制截断,不是 Jackson 实际收到的报文。

使用 v5 脚本按设备采集解析前原文:

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 到文件:

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,再取下一段:

DBMS_LOB.SUBSTR(rule_content, 20000, 20001)

第二步:在代码中拿到 Jackson 的真实输入

当前主链路:

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

相关源码:

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

对每条失败样本保存以下信息即可:

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 路径才会再次执行对象边界截取。

天眼与微步的现有复现入口

测试文件:

syslog-consumer-rule/src/test/java/com/Modules/NormalData/LogNormalProcessorRuleSelectionTest.java

直接参考两个用例:

  • threatbookUsesTdpRuleAfterFixedOffsetHoneypotRulesAreRejected:微步 Threatbook 报文应选择规则150, 不能被158/159的固定偏移规则提前判为非标;
  • skyeyeCropperPrefixIsNarrowedToOuterJsonBeforeJackson:天眼规则129截取后,Jackson 输入必须是完整外层 JSON。

把现场完整原文脱敏后增加为 fixture,并使用现场规则的真实 cropper_params。至少断言:

选中的 normalize_rule_id
一个关键字段的映射值
未生成“JSON解析异常”非标记录

使用 JDK 8 执行:

mvn -Dtest=LogNormalProcessorRuleSelectionTest test

必看的历史修改

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 对象边界截取,避免前一条不适用规则失败后立即转非标。

判断问题是否由删除旧修复引起,必须对同一份完整原文记录四项结果:

当前首次解析结果
旧版首次解析结果
旧版修复后的字符串(并指出实际生效步骤)
旧版第二次解析结果

没有这四项证据,不要恢复旧版全局修复。全局恢复会给每条坏 JSON 增加多次扫描、正则替换、字符串分配和 第二次 Jackson 解析。若确实需要兼容设备缺陷,优先限定到指定设备及指定规则,最后才考虑通用解析器。