Skip to content

Vue 多正则日志过滤面板:把每条规则当作独立分类器 ​

译注:本文编译自 dev.to 作者 begoodtool 的技术分享,原文标题为《Log Multi-Regex Filter Dashboard》,原文链接见文末。文章介绍了一个基于 Vue 的日志过滤组件实现思路,核心是把每条正则规则视为独立的行分类器,而非反复搜索同一份日志。

排查线上事故时,开发者常常要在 Ctrl+F、shell 命令和电子表格之间来回切换,只为回答几个基本问题:有多少行提到 ERROR?哪些请求来自某个 IP 段?修复之后警告数量是否下降?真正麻烦的不是写一条正则,而是把多条正则应用到同一份粘贴进来的日志上,同时不丢失行号,也不让同一行在不同搜索中被重复计数。

作者给出的心智模型不是「多次搜索日志」,而是「把每条规则变成一个小而独立的分类器,再比较各自命中的行集合」。这一区别解释了该 Vue 组件中的若干实现选择。

一条规则 = 标签 + 模式 + 颜色 ​

界面默认提供三条规则,每条规则包含标签、正则模式和颜色:

javascript
const COLORS = ["#e53e3e", "#dd6b20", "#d69e2e", "#38a169", "#3182ce"];

const defaultRules = [
  { label: "ERROR", pattern: "ERROR|FATAL|Exception|error", color: COLORS[0] },
  { label: "WARN", pattern: "WARN|WARNING", color: COLORS[1] },
  { label: "INFO", pattern: "INFO", color: COLORS[3] },
];

const rules = ref(defaultRules.map((r) => ({ ...r })));

这里的展开运算符看似微小却很关键:它让响应式状态持有全新的对象,避免编辑时直接改动模板常量。新增规则上限为十条,颜色按位置分配。空模式在分析时会被忽略,因此在试验阶段新增一行不会立刻报错。

标签只用于展示,不参与匹配逻辑。若标签为空,结果会回退显示模式本身,这样未命名规则在摘要和导出文件名中依然可用,而像 Specific IP 这样的可读标签能让事故报告更易扫读。

匹配行,而不是统计出现次数 ​

分析过程只把输入切分一次,然后为每条非空规则构造一个 JavaScript RegExp:

javascript
const lines = logText.value.split("\n");

for (const rule of rules.value) {
  if (!rule.pattern.trim()) continue;

  let re;
  try {
    const flags = caseSensitive.value ? "g" : "gi";
    re = new RegExp(rule.pattern, flags);
  } catch {
    errorMessages.value.push(
      t("logRegexDashboard.errorInvalidRegex")
        .replace("{label}", rule.label || rule.pattern)
    );
    continue;
  }

  const matches = [];
  for (let i = 0; i < lines.length; i++) {
    re.lastIndex = 0;
    if (re.test(lines[i])) {
      matches.push({ lineNo: i + 1, line: lines[i] });
    }
  }
}

g 标志会让 test() 变成有状态的:一次成功匹配后,lastIndex 可能指向下一次测试起点之后。每行测试前重置 re.lastIndex 才能保证结果确定。这个细节很容易被忽略,因为同一条表达式只测一次时往往看起来正常。

注意这里统计的是「匹配的行」,而不是每次出现。一行里包含 ERROR ERROR ERROR 只给 ERROR 规则贡献一次命中。对分诊面板而言这是有意为之——「有多少条日志受影响」通常比原始子串频率更有用。不同规则可以重叠,ERROR 与 Specific IP 的计数相互独立,而非互斥分类。

无效模式不会中断整轮分析。try/catch 会记录一条局部错误并继续处理其他规则。当某条试验性模式有未闭合的 [,而其余事故检查仍然有效时,这种失败方式要友好得多。

结果是可以检视的数据,而不只是进度条 ​

每条结果都保留原始行文本和从 1 开始的行号。进度条宽度相对于最大的规则计数:

javascript
const maxCount = computed(() => {
  if (!results.value.length) return 1;
  return Math.max(...results.value.map((r) => r.matches.length), 1);
});

width: maxCount > 0
  ? (res.matches.length / maxCount * 100) + "%"
  : "0%"

也就是说,图表回答「哪条规则命中的行最多」,可展开列表回答「具体是哪些条目造成了这个数字」。导出路径同时保留两部分:

javascript
const lines = [`=== ${res.label} (${res.pattern}) ===`, ""];
res.matches.forEach((m) => {
  lines.push(`[Line ${m.lineNo}] ${m.line}`);
});
const blob = new Blob([lines.join("\n")], {
  type: "text/plain;charset=utf-8",
});

浏览器创建临时对象 URL 并下载 log-filter-${res.label}.txt。组件不会把粘贴的文本发送到服务器,全部工作在页面内完成。

实际限制 ​

这是一个面向行的过滤器,不是日志解析器。它不理解 JSON 字段、多行堆栈、时间戳,也不理解正则表达式之外的日志级别。因此跨多行的堆栈可能被计为多条互不相关的条目。正则语法即 JavaScript 语法,包括其转义规则,病态模式依然可能很耗时。

实现上还会为每条规则扫描每一行。十条规则对上数万行会让浏览器变慢,结果列表也会把匹配文本保留在内存中。面对超大粘贴内容时,建议先用宽泛规则、检视缩小后的集合,再运行更具体的模式。

作者已将这一思路做成免费小工具:Log Multi-Regex Filter Dashboard。当你需要可比较的逐规则行数以及其背后的实际行内容,又不想先搭建日志管道时,它会很有用。

原文链接 ​

https://dev.to/begoodtool/log-multi-regex-filter-dashboard-153g