以前我没得选,现在我只想做个坏人

花了一天时间Debug的问题是什么样子?

    大前端     大前端·Debug·调试·Charles·Fiddler·

  1. 发现问题
  2. 复现问题
  3. Debug
  4. 两个问题
  5. 另一个奇葩问题

昨天测试同学反馈说接口状态埋点上报的参数中有两个不正常。我简单排查了下,重现了问题,但不明白为什么如此。今天和测试同学差不多花了一天的时间,终于解决了,但答案却让人无语。

什么是(数据)埋点?

请看前端埋点之曝光实现)

什么是接口状态埋点?

顾名思义就是监控接口的响应状态(比如status,timeout,responseText,根据各自需求来确定),并且获取到预先挑选的数据,上报给服务端。

发现问题

为了监控接口的异常状态,前端在接口响应时选择将拿到的xhr.status,request url,res.code(后端业务的逻辑code)上报给服务端。

后端其实也有监控,但考虑到有时问题其实发生在前后端中间的某些地方,所以在直接面向用户的前端这边上报接口数据就是非常必要的。

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
xhr.onloadend = () => {
let res: Response | { code: number } = { code: -1 };
try {
res = JSON.parse(xhr.responseText);
} catch (error) {
console.log('request url: ' + url);
console.error(error);
} finally {
// 埋点:数据上报
report('response_status', {
xhr_status: xhr.status, // 200、400、500等
url: encodeURIComponent(requestUrl),
response_code: res.code
});
}
resolve(res);
};

但测试同学通过Fiddler将接口的响应Status修改为500(400、502等)之后,告诉我说Fiddler显示上报请求中xhr_status参数为0

复现问题

遇到问题之后,我立刻在电脑直接打开H5页面(内网测试环境)尝试复现。我使用Charles(Fiddler没必要macOS版本)的Rewrite功能将接口响应的Status修改为500,然后前端打log/断点。

对于我来说,有三个环境需要说明下。

  1. 开发环境。本地开发页面的环境,通过localhost来访问页面。
  2. 测试环境。开发完成后将打包后的资源文件部署到内网某服务器,通过正常的链接:https://a.b.com/c/d/e来访问。
  3. 外网。将测试环境验证没问题的代码部署到外网服务器,直接面向用户。

啊哦,没有复现!

然后我就去测试同学那边,让她给我演示一遍。

她开始操作:Fiddler–>映射Response Body–>定向到本地的JSON文件;同时修改Reponse Status Code500 Internal Server Error;接着操作手机,发起请求。

啊哦,上报请求中的xhr_status参数依旧为0。(看来不是测试同学看错了,哈哈哈哈哈)

可达鸭眉头一皱,发现事情并不简单.jpg

接着,又测试了400、404、502,问题一样。

Debug

然后我又回到位置上,电脑上打开项目开发环境的页面,Charles修改Reponse Status Code,然后发现还是正常

我通过Charles修改电脑浏览器发出的请求时还遇到了浏览器开发者工具那边没有显示修改过的请求头信息的问题,还以为我Charles用错了,折腾了一会儿,最后冷静下来,想了想才明白过来不是我设置的问题。关于这个坑,我记录在Segmentfault了,可以点过去看看。

我开始认真了。去查了下文档,先弄清楚status0代表什么意思吧:

只读属性 XMLHttpRequest.status 返回了XMLHttpRequest 响应中的数字状态码。status 的值是一个无符号短整型。在请求完成前,status的值为0。值得注意的是,如果 XMLHttpRequest 出错,浏览器返回的 status 也为0

所以我开始想为什么请求会没有完成?或者说请求为什么会出错?

一时没有思路。

又跑去测试同学那边,看着她再把所有的情况再测试一遍。值得一提的是,这次我留意到,就算测试同学通过Fiddler把Reponse Status Code修改为200(对,就是200,再用抓包工具修改一次),依旧拿不到xhr.status。同时发现,如果完全不使用Fiddler,那么服务器那边的log就能显示上报的xhr.status是正常取到的(是200)。

那如果把服务器上的后端程序直接停掉呢?

页面接口的后端程序和埋点上报的后端不在一起,分开的。

测试的结果是,前端拿不到xhr.status

你一定开始晕了,我当时也有点懵逼。让我来理一下当时测试的所有情况:

  1. 服务器正常,不经过任何抓包工具,能拿到xhr.status
  2. 服务器停掉,不经过任何抓包工具,拿不到xhr.status
  3. 服务器正常,通过Fiddler修改Reponse Status Code,拿不到xhr.status
  4. 服务器正常,通过Charles修改Reponse Status Code,能拿到xhr.status

这样的测试结果,不禁让我开始怀疑起Fiddler。是不是它在实现修改Headers功能的时候,emmmm,有bug?否则为什么用Charles修改就可以,Fiddler却不行呢(3和4对比)?而且不用Fiddler就正常,用了就不正常(1和3对比),更说明是你的锅了呀。

但我并没有死心,回到位置上继续排查。

一顿操作(关掉Chrome扩展ModHeader),我注意到本地开发环境也复现了,同时我留意到控制台出现一个跨域错误,我想我知道答案了。

因为请求遇到了跨域限制,所以浏览器中止XMLHttpRequest请求并抛出一个异常,即:XMLHttpRequest 出错,浏览器返回的 status 为0。

两个问题

但为什么之前本地开发环境测试时就没有问题呢?

这是因为我早期在开发这个项目时用了测试环境的线上接口。后端没有对来自localhost的请求设置ACAO头,所以我为了开发方便通过Chrome扩展ModHeader自己修改了Reponse Header,添加上了Access-Control-Allow-Origin: http://localhost:8080。这就是为什么我开始测试时没有遇到跨域限制,xhr.status正常的原因。

但这不能解释为什么我用Charles修改响应头能拿到,但测试同学通过Fiddler修改却不行。

毕竟测试同学是直接在手机上操作的,不像我在本地开发环境操作,不会有跨域限制。

然后我又跑去测试同学那里和她讨论。我还是怀疑是Fiddler有问题,那么还能怎么测试呢?我让测试同学不用Fiddler修改Response Status CodeResponse body,就只修改请求的链接,让它是错误的路径。

这次发现上报的埋点数据里,有xhr_status(404)了。然后修改回去,拿不到。

这下能基本确定了,是Fiddler修改Response Status Code功能的锅。

我其实想这个时候用Chrome的Inspect功能来确认下页面的控制台有没有跨域错误来着(应该是有)。但奈何换了好几个手机都不能在测试同学的Chrome上正常调试(现象是能检测到网页的Tab,但点击inspect后,弹框空白(翻墙了,清理过Chrome那个xxx,手机没有使用其他内核),应该是需要找一个特殊的Chromium版本才可以。太麻烦了,既然基本解决了就不浪费精力了。)

搞了半天是Fiddler的锅。我就说嘛,我写的代码怎么可能有Bug?

另一个奇葩问题

这时候基本上是下午4、5点了。然后又遇到了另一个奇葩的问题。

现象是:后台一个页面的某块字体样式不正常(变大并且靠左偏了)!

我迅速去自己电脑上(测试环境)登陆后台看了下,一切正常。奇怪了。我只能去测试同学电脑上调试了。

首先打开开发者工具,通过审查元素查看问题元素的HTML和样式,一般都是样式不正常。比如多了些样式/少了些样式。但我这次不止如此,我先是注意到HTML结构很奇怪。我没有使用<font />标签的习惯。但他喵的哪来的<font />

我还注意到有些陌生的带有class的元素(代码是我写的,但对这一块的布局很陌生),我copy该class,刷新页面(因为你打开开发者工具的时候可能有些资源文件已经加载完了,所以不会显示在开发者工具里),然后在NetworkCtrl+F搜索所有的资源文件,看他喵的这个class是哪个狗日的注入的。

然后就看到了translate.googleapis.com域名下的某js文件包含该class

我:???

一看到这个域名,测试同学就说”Google翻译”,然后我们才注意到网页上方的Google翻译工具 不见图请翻墙

左键–>选项–>一律不翻译此网站

刷新页面,搞定。

顺便一提,对于所有的线上样式问题,调试思路大都如此。

page PV:  ・  site PV:  ・  site UV: