用 1300 行原生 JS 做了一个 Chrome DevTools 扩展,让前后端不再为接口报错截图扯皮

TL;DR: Network Error Reporter 是一个 Chrome DevTools 扩展,把失败的网络请求一键转成结构化 Markdown 报告和可分享的 PNG 图片。零依赖,纯原生 JavaScript,两个文件搞定全部逻辑。本文拆解它的架构决策、Canvas 图片导出方案和敏感数据检测设计。

本文假设你了解 Chrome 扩展的基础概念(manifest.json、DevTools panel),但不需要有扩展开发经验。

问题:截图 + 手写说明 = 低效沟通

前端遇到接口报错时,团队的沟通路径通常是这样的:

  1. 打开 DevTools,找到那条红色请求
  2. 截图 Network 面板
  3. 再截一张 Headers 面板
  4. 打开 IM,贴图,手写一段"我在某某页面点了保存,接口 502 了"
  5. 后端回复:"Request ID 呢?""Trace ID 发一下?""Request Body 是什么?"
  6. 再来一轮截图

这个来回的核心问题不是技术,而是信息结构------截图是非结构化的,每次都要人肉提取和补充关键字段。

方案:把 HAR 数据转成结构化报告

思路很直接:Chrome DevTools 扩展能拿到 HAR 格式的请求数据(chrome.devtools.network.getHAR()),把它结构化整理成 Markdown 和图片就行了。

最终的产品形态是 DevTools 里的一个三栏面板:

scss 复制代码
┌──────────────┬──────────────────┬──────────────────┐
│   请求列表    │   补充上下文      │    报告预览       │
│  (Rail)      │  (Compose)       │   (Preview)      │
│              │                  │                  │
│ ● 500 POST   │ 影响范围: [多用户] │  环境信息         │
│   /api/save  │ 频率: [持续性]    │  错误详情         │
│              │ 复现步骤: ...     │  请求与响应明细    │
│ ○ 200 GET    │ 预期结果: ...     │  影响评估         │
│   /api/list  │ 实际结果: ...     │  复现说明         │
└──────────────┴──────────────────┴──────────────────┘

选一条请求 → 自动生成报告 → 复制 Markdown 或导出图片。

架构:两个文件,职责分明

整个项目只有两个核心 JS 文件,加起来 ~1300 行:

文件 职责 行数
panel/report.js 数据层:HAR 归一化、Markdown 生成、敏感检测 ~450
panel/main.js 视图层:请求列表、表单交互、DOM 预览、Canvas 导出 ~850

这个分层不是随意的。report.js 是纯函数------输入 HAR entry 和表单状态,输出 Markdown 和预览数据结构,不碰 DOM,不碰 Chrome API。main.js 负责所有副作用:Chrome DevTools API 调用、DOM 渲染、剪贴板操作。

scss 复制代码
chrome.devtools.network ──→ main.js ──→ report.js
                              │              │
                              │         normalizeEntry()
                              │         buildMarkdownReport()
                              │              │
                              │              ▼
                              │       { markdown, preview,
                              │         sensitiveHits }
                              │              │
                              ▼              ▼
                         DOM 渲染 ←──── preview 数据
                         Canvas 导出
                         剪贴板写入

为什么不用框架? 两个原因。第一,DevTools panel 的生命周期很简单------打开面板就加载,关闭就销毁,没有路由、没有复杂状态同步,原生 DOM 操作完全够用。第二,零构建依赖意味着 Load unpacked 就能跑,改一行代码刷新面板就生效,开发体验反而比加了打包工具更快。

技术决策一:请求归一化

Chrome 的 HAR entry 格式冗长且不稳定------不同版本的 Chrome 可能在 _resourceTyperesourceType 字段上有差异。normalizeEntry() 把它收敛成一个干净的内部结构:

javascript 复制代码
// panel/report.js
function normalizeEntry(entry) {
  const request = entry.request ?? {};
  const response = entry.response ?? {};
  const status = Number(response.status ?? 0);

  return {
    id: [method, url, startedAt, entry.time ?? 0].join("::"),
    method,
    url,
    path: safePathname(url),
    status,
    statusText: response.statusText || (status === 0 ? "Network Error" : ""),
    timeMs: Number(entry.time ?? 0),
    requestHeaders: normalizeHeaders(request.headers),
    responseHeaders: normalizeHeaders(response.headers),
    resourceType: normalizeResourceType(entry, mimeType, url),
    isFailure: status >= 400 || status === 0
  };
}

几个值得注意的点:

请求去重用的是复合 ID[method, url, timestamp, duration].join("::")。HAR 里同一个请求可能因为重定向或重试出现多次,用这四个字段组合能有效区分。

资源类型有两层回退 :先读 _resourceType(Chrome 私有字段),拿不到就通过 MIME type 和 URL 模式推断。这样即使 HAR 格式变动,分类功能也不会挂。

javascript 复制代码
function normalizeResourceType(entry, mimeType, url) {
  // 第一优先级:Chrome 的私有字段
  const rawType = String(entry._resourceType || entry.resourceType || "").toLowerCase();
  if (rawType === "fetch" || rawType === "xhr") return "fetch-xhr";
  if (rawType === "document") return "document";
  // ...

  // 第二优先级:MIME type + URL 推断
  if (lowerMime.includes("json") || /\/api\//.test(lowerUrl)) return "fetch-xhr";
  if (lowerMime.includes("text/html")) return "document";
  // ...
}

内存上限 200 条,按时间倒序排列。DevTools 面板不适合持有太多数据------它本身就在一个受限的执行上下文里。

技术决策二:Markdown 报告生成

buildMarkdownReport() 做两件事:生成 Markdown 文本和一个结构化预览对象。

报告不是把 HAR 原样搬过来,而是做了信息提炼

Header 分组 :自动把 headers 分成「鉴权/链路头」和「业务自定义头」两组。标准 HTTP 头(AcceptContent-TypeUser-Agent 等)直接过滤掉------后端不需要看这些。

javascript 复制代码
function splitHeaderGroups(headers, side) {
  const excluded = new Set([
    "accept", "accept-encoding", "accept-language", "cache-control",
    "content-type", "cookie", "host", "origin", "user-agent", "vary"
    // ...
  ]);

  const filtered = headers.filter(h => {
    const name = h.name.toLowerCase();
    if (excluded.has(name)) return false;
    return name.startsWith("x-") || name.startsWith("trace")
      || name.startsWith("auth") || name.startsWith("gateway")
      // ...
  });

  return {
    trace: filtered.filter(h => isTraceHeader(h.name)),
    business: filtered.filter(h => !isTraceHeader(h.name))
  };
}

Trace ID 自动提取 :支持 7 种常见 trace header(x-request-idtraceparentx-b3-traceid 等),优先从 response headers 取------因为很多网关是在响应时才注入 trace ID。

JSON 响应智能摘要 :不是把完整 response body 贴上去,而是优先提取 codemessageerrorrequestId 等关键字段。大数组只展示第一个元素和总长度。

javascript 复制代码
function summarizeJson(value) {
  if (Array.isArray(value)) {
    const sample = value.length > 0 ? JSON.stringify(value[0], null, 2) : "[]";
    return `type: array\nlength: ${value.length}\nsample: ${truncateLargeBlock(sample)}`;
  }

  const importantKeys = [
    "code", "status", "success", "message", "msg",
    "error", "errorCode", "errorMessage", "requestId", "traceId", "timestamp"
  ];
  // 优先提取这些字段,没有的话取前 8 个
}

技术决策三:Canvas 图片导出

这是整个项目最复杂的部分。为什么不用 html2canvas 或者截图 API?因为 DevTools panel 的安全策略限制了大部分 DOM-to-image 方案,而且引入第三方库违背了零依赖原则。

方案是手动测量文本布局,然后用 Canvas 2D 绘制

流程分两步:

第一步:测量布局

用一个离屏 canvas 的 measureText() 来计算每个文本块换行后的实际高度,得出整张图片的像素尺寸。

javascript 复制代码
function measurePreviewLayout(preview) {
  const width = 1080;  // 固定宽度,适配即时通讯工具
  const measureCanvas = document.createElement("canvas");
  const measureCtx = measureCanvas.getContext("2d");

  // 逐字符测量换行
  function measureWrappedLines(ctx, text, font, maxWidth) {
    const paragraphs = text.split("\n");
    const lines = [];
    ctx.font = font;

    paragraphs.forEach(paragraph => {
      let current = "";
      for (const char of paragraph) {
        const candidate = current + char;
        if (!current || ctx.measureText(candidate).width <= maxWidth) {
          current = candidate;
          continue;
        }
        lines.push(current);
        current = char;
      }
      if (current) lines.push(current);
    });

    return lines.length ? lines : ["无"];
  }
  // ...
}

为什么逐字符测量? 因为报告里有大量中英文混排和 URL,按单词分割不适用。逐字符虽然慢一点,但对混合文本最准确。

第二步:绘制

按照测量结果,用 Canvas 2D 的基础 API 绘制圆角矩形、分割线、表格和文本。调色板用的是 Material Design 3 的色值。

javascript 复制代码
function drawPreviewCanvas(ctx, metrics) {
  const palette = {
    background: "#f7f2fa",
    section: "#fffafb",
    sectionBorder: "rgba(121, 116, 126, 0.16)",
    text: "#1d1b20",
    muted: "#625b66",
    monoBg: "#f3edf7"
  };

  // 逐 section 绘制:环境信息、错误详情、请求明细...
  metrics.blocks.forEach((block) => {
    drawSectionFrame(ctx, ...);
    if (block.type === "list") drawListSection(ctx, ...);
    else if (block.type === "table") drawTableSection(ctx, ...);
    else if (block.type === "blocks") drawBlocksSection(ctx, ...);
    // ...
  });
}

导出策略也有回退机制:

lua 复制代码
尝试 navigator.clipboard.write(ClipboardItem) → 成功:图片到剪贴板
                                               → 失败:自动下载 .png 文件

图片固定宽度 1080px、2x 设备像素比,在微信、飞书等 IM 工具里显示效果清晰。

技术决策四:敏感数据检测

在把报告发出去之前,自动扫描是否包含敏感信息:

javascript 复制代码
const SENSITIVE_PATTERNS = [
  /authorization/i,        // 认证头
  /cookie/i,               // Cookie
  /set-cookie/i,           // Set-Cookie
  /token/i,                // Token 相关头
  /bearer\s+[a-z0-9\-_.]+/i,  // Bearer 令牌值
  /\b1[3-9]\d{9}\b/,      // 中国大陆手机号
  /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i  // 邮箱地址
];

检测到后不自动脱敏 ------只弹出确认提示,告诉用户发现了几类敏感字段。自动脱敏听起来更安全,但实际上会导致信息丢失,有时候 Authorization 头本身就是排查问题的关键。这里把决策权留给用户。

没有自动化测试?

是的。当前只有 node --check 语法检查------连单元测试都没有。

这是一个有意识的取舍。report.js 的函数都是纯函数,后续加测试非常容易。但项目早期,手动验证(在 DevTools 里实际点一遍)反而能覆盖更多 Chrome API 的集成场景。自动化测试的优先级会在功能稳定后提上来。

你不需要框架、不需要依赖

这个项目刻意保持极简:

  • 零 npm 依赖 :没有 package.json,没有 node_modules
  • 零构建步骤Load unpacked 直接加载项目目录
  • 两个核心文件:数据层 + 视图层,职责清晰

对于一个 DevTools 工具来说,这是正确的复杂度。不是每个项目都需要 React + Webpack + TypeScript。当你的状态管理就是一个 plain object、UI 更新就是重新渲染一个列表,原生 JS 完全够了。

后续方向

当前版本是 0.2.0,scope 故意收窄到单请求报告。后续可能的方向:

  • 多请求聚合(同一个接口连续 5xx 的趋势)
  • 自定义报告模板
  • 与团队工具集成(直接创建 issue)

但这些都不会改变核心架构------report.js 负责数据,main.js 负责视图,保持这个边界比任何新功能都重要。


Network Error Reporter 是一个 MIT 协议的开源项目。如果你的团队也被接口报错的截图沟通困扰,试试看。

项目地址:GitHub - network-error-reporter

相关推荐
A_Qyp1 小时前
JeechBoot前端表格内操作设置下拉
前端·javascript
YimWu1 小时前
面试官:OpenCode Agent 代理机制了解吗?
前端·agent·ai编程
小小程序员mono2 小时前
JS 与 Vue Router 导航方式对比
开发语言·javascript·vue.js
IT星宿2 小时前
smart-unit:一个优雅的 JavaScript 单位转换库,告别繁琐的依赖管理
前端·javascript·typescript
Sgf2272 小时前
如何阅读 React 源码:系统化学习指南
前端·react.js·前端框架
李剑一2 小时前
解决 Cesium 网络卡顿!5 分钟加载天地图,内网也能流畅用,附完整代码
前端·vue.js·cesium
QD_ANJING2 小时前
3月面大厂前端岗总结笔记(含答案)
前端·javascript·笔记·面试·职场和发展·前端框架·pdf
YimWu2 小时前
面试官:OpenCode Prompt 系统了解吗?
前端·agent·ai编程
百锦再3 小时前
复杂查询中基于代价的连接条件下推实践与思考
前端