抓包调试实战:从入门到高效排查网络问题
为啥要自己写个抓包调试工具?
上个月接手一个老项目,前端用的是 Vue 2 + Axios,后端是 PHP。问题出在:用户反馈某些操作没反应,但控制台和 Network 面板里啥都看不到。一开始以为是代码逻辑问题,debug 了两天,发现其实是接口返回了 200,但数据结构变了,而前端没做兼容处理。
问题是,这类“静默失败”太难排查了——既没报错,又没日志。Chrome DevTools 虽然能看请求,但刷新就没了,没法回溯。更麻烦的是,测试环境和生产环境行为不一致,本地复现不了。于是我想:能不能在应用里加个轻量级的“请求记录器”,把所有发出去的请求和响应都存下来,方便随时查看?
网上搜了一圈,现成的库要么太重(比如引入整个监控 SDK),要么只能看当前页面的请求。最后决定自己撸一个,核心目标就两个:1)不侵入业务代码;2)能导出历史记录。
最开始的方案:Axios 拦截器 + localStorage
思路很简单:在 Axios 的请求和响应拦截器里,把关键信息(URL、参数、状态码、响应体)存到 localStorage。代码很快就写出来了:
// requestLogger.js
const MAX_LOGS = 50; // 最多存50条,避免爆内存
function saveLog(log) {
const logs = JSON.parse(localStorage.getItem('api_logs') || '[]');
logs.unshift(log);
if (logs.length > MAX_LOGS) logs.length = MAX_LOGS;
localStorage.setItem('api_logs', JSON.stringify(logs));
}
// 请求拦截器
axios.interceptors.request.use(config => {
config.meta = { startTime: Date.now() };
return config;
}, error => Promise.reject(error));
// 响应拦截器
axios.interceptors.response.use(response => {
const { config, status, data } = response;
const duration = Date.now() - config.meta.startTime;
saveLog({
url: config.url,
method: config.method,
params: config.params || {},
data: config.data || {},
status,
responseData: data,
duration,
timestamp: new Date().toISOString()
});
return response;
}, error => {
if (error.response) {
saveLog({
url: error.config.url,
method: error.config.method,
status: error.response.status,
error: error.message,
timestamp: new Date().toISOString()
});
}
return Promise.reject(error);
});
亲测有效!打开控制台,输入 JSON.parse(localStorage.api_logs) 就能看到所有请求记录。但很快发现了问题……
最大的坑:性能问题和内存泄漏
上线测试环境后,QA 同事反馈页面变卡了。我一开始不信,直到自己点开一个高频刷新的监控页面——每秒发 3 个请求,localStorage 里的日志疯狂增长,浏览器直接卡死。
问题出在哪儿?一是每次存 localStorage 都要序列化整个数组,50 条记录还好,但高频请求下这操作太重;二是响应体里可能有大对象(比如返回几百 KB 的 JSON),全存下来内存吃不消。
折腾了半天,做了三处优化:
- 只存必要字段:砍掉完整的
responseData,只保留前 200 个字符,或者用JSON.stringify(data).slice(0, 200); - 改用 IndexedDB:localStorage 是同步阻塞的,IndexedDB 是异步的,对主线程更友好;
- 加开关:默认关闭,通过 URL 参数
?debug=1开启,避免影响普通用户。
但 IndexedDB 写起来有点啰嗦,我又不想引入新依赖。最后折中方案:还是用 localStorage,但限制单条日志大小,并且只在 debug 模式下记录。
核心代码就这几行
调整后的精简版(带开关和大小限制):
// utils/debugLogger.js
const DEBUG_MODE = new URLSearchParams(window.location.search).has('debug');
const MAX_LOG_SIZE = 1024 * 5; // 单条日志不超过5KB
function truncateObj(obj, maxLen = 200) {
try {
const str = JSON.stringify(obj);
return str.length > maxLen ? str.slice(0, maxLen) + '...' : str;
} catch {
return '[Circular or Non-serializable]';
}
}
function saveLog(log) {
if (!DEBUG_MODE) return;
const logStr = JSON.stringify(log);
if (logStr.length > MAX_LOG_SIZE) return; // 超大日志直接丢弃
try {
const logs = JSON.parse(localStorage.getItem('api_logs_debug') || '[]');
logs.unshift(log);
if (logs.length > 30) logs.length = 30; // debug 模式只存30条
localStorage.setItem('api_logs_debug', JSON.stringify(logs));
} catch (e) {
console.warn('Failed to save debug log', e);
}
}
// 在 Axios 初始化后调用
export function initDebugLogger() {
if (!DEBUG_MODE) return;
axios.interceptors.request.use(config => {
config.meta = { startTime: Date.now() };
return config;
}, error => Promise.reject(error));
axios.interceptors.response.use(response => {
const { config, status, data } = response;
saveLog({
url: config.url,
method: config.method?.toUpperCase(),
params: truncateObj(config.params),
body: truncateObj(config.data),
status,
response: truncateObj(data),
duration: Date.now() - config.meta.startTime,
time: new Date().toLocaleString()
});
return response;
}, error => {
if (error.config) {
saveLog({
url: error.config.url,
method: error.config.method?.toUpperCase(),
status: error.response?.status || 'ERR',
error: error.message,
time: new Date().toLocaleString()
});
}
return Promise.reject(error);
});
}
使用时,在 main.js 里加一行:
import { initDebugLogger } from './utils/debugLogger';
initDebugLogger(); // 只有 ?debug=1 时才生效
现在,只要在 URL 后加 ?debug=1,就能自动记录最近 30 条请求,打开控制台执行 copy(JSON.parse(localStorage.api_logs_debug)) 直接复制到剪贴板,甩给后端同事超方便。
还有两个小问题没解决
虽然基本能用,但有两个瑕疵一直没动:
- 如果请求被取消(比如组件卸载时 cancel token),拦截器不会触发,这条请求就漏记了;
- 文件上传的请求体(FormData)没法序列化,会变成
{},不过我们项目里上传走的是单独通道,影响不大。
其实可以监听 XMLHttpRequest 的原生事件来覆盖所有情况,但那样就得重写整个请求层,成本太高。目前这个方案已经够用——毕竟只是调试辅助,不是生产级监控。
回顾与反思
回头看,这个小工具最大的价值不是技术多牛,而是让问题可追溯。以前遇到“偶发性接口异常”,只能靠人肉复现,现在直接看日志就能定位是哪次请求出了问题。而且因为不侵入业务代码,后续想关掉也只需删一行 import。
做得好的地方:轻量、按需开启、日志可导出。还能优化的点:加上时间范围筛选、支持按状态码过滤,或者做个简单的 UI 面板(现在还得开控制台)。不过目前团队用着挺顺手,暂时没动力加新功能。
以上是我踩坑后的总结,希望对你有帮助。如果你有更好的实现方式,比如怎么优雅地处理 FormData 或取消请求的日志,欢迎评论区交流!

暂无评论