学习前提

完成分组、替换、Unicode与锚点;理解输入长度限制和失败分支。

本课完成后你能做什么

分层解析简单日志,保留错误行号并限制输入规模。

先理解机制

先定义格式:LEVEL name=<ASCII标识符> ms=<整数>。LEVEL仅允许INFO或WARN,字段顺序固定。这是自定义平面格式,不是通用日志、JSON或带引号CSV解析器。

正则负责结构与字符范围,普通代码负责数字范围和累计。原始行号先保留,再处理失败,避免过滤后重编号让错误位置失真。

嵌套且重叠的量词如(a+)+在一些回溯引擎中可能导致灾难性回溯。优先选择有界、不含歧义的模式,限制输入长度,不在主线程运行用户提供的任意模式。惰性量词不能自动解决这类问题。

本例每行最多120个UTF-16代码单元,名称最多20位、耗时最多5位,并额外要求数值不超过10000。长度限制是教学程序的边界,不代表完整的服务端防滥用方案。

可运行示例

const rows = [
  "INFO name=python ms=25",
  "WARN name=sql ms=40",
  "INFO name=python ms=35",
  "INFO name=java ms=20000",
  "DEBUG name=sql ms=10"
];
const pattern = /^(?<level>INFO|WARN) name=(?<name>[a-z][a-z0-9_-]{0,19}) ms=(?<ms>0|[1-9]\d{0,4})$/;
const totals = new Map(), rejected = [];
rows.forEach((row, index) => {
  const match = row.length <= 120 ? pattern.exec(row) : null;
  if (!match || Number(match.groups.ms) > 10000) {
    rejected.push(index + 1);
    return;
  }
  const {name, ms} = match.groups;
  totals.set(name, (totals.get(name) || 0) + Number(ms));
});
console.log(JSON.stringify([...totals].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)));
console.log(JSON.stringify(rejected));

推演与验证

python合计60,sql合计40。第4行格式正确但数值越界;第5行级别不在白名单,拒绝行号[4,5]。

常见错误与边界

不要用正则强行解析嵌套JSON或HTML,选择相应解析器。若输入增长为海量日志,应考虑流式处理和累计溢出,不是仅扩大量词上限。

动手练习

添加INFO name=java ms=0、INFO name=sql ms=01和超过120字符的一行,推演结果。

参考解法与理由

java会以总计0进入结果;01违反前导零规则,超长行在正则执行前就被拒绝。新错误行号为[4,5,7,8],原有效课程总计不变。

进一步阅读