Whidy Writes

开发者需要掌握的解决BUG的技巧 - 分析报错信息

创建于:最后更新:Whidy

由于太久没有用自己的笔记本搞开发了,昨天准备在博客上写点东西,突然发现本地程序运行不起来了。最诡异的是,同样的代码,在服务器和另一台电脑上上运行都是正常的,我也不记得太久之前,笔记本究竟发生了什么。东西总是要写的,问题必须解决,虽然最终是解决了,但是还是花了我将近半天多的时间,回头来看,在此期间,我尝试过的各种解决办法,非常适合写一篇几乎适用于所有开发者在开发中遇到问题时的解决思路。就有了此文。文中我也会尽可能考虑到每一个细节并做提醒,这些细节往往需要大量的实践经验。

场景描述

问题出现在我运行本地博客系统( Gatsby.js )时报错。无法正常启动本地服务,后端服务使用程序是 Strapi

环境概述如下:

  • macOS Monterey (12.3.1)
  • Node.js (v16.14.0)
  • Gatsby.js (v4.9.1/v4.12.1)
  • Strapi (v4.1.2/v4.1.7)
  • PostgreSQL (v14.2)
  • zsh (lastest)

PS: 为什么上面有的有两个版本,因为我以为是小版本的升级导致错误,实际上与此无关,另外,放出PostgreSQL的版本信息,只是顺便提醒大家在node.js下,有些数据库驱动程序的升级或变化也有可能造成影响,需要注意。

错误信息

一如既往的执行 npm run dev ,然后就一大串报错了,我们看最先出错的部分报错信息:

... success createSchemaCustomization - 0.002s ERROR #11321 PLUGIN "gatsby-source-strapi" threw an error while running the sourceNodes lifecycle: Request failed with status code 502 Error: Request failed with status code 502 - createError.js:16 createError [gatsby-blog]/[gatsby-source-strapi]/[axios]/lib/core/createError.js:16:15 - settle.js:17 settle [gatsby-blog]/[gatsby-source-strapi]/[axios]/lib/core/settle.js:17:12 - http.js:295 IncomingMessage.handleStreamEnd [gatsby-blog]/[gatsby-source-strapi]/[axios]/lib/adapters/http.js:295:11 - node:events:532 IncomingMessage.emit node:events:532:35 - readable:1346 endReadableNT node:internal/streams/readable:1346:12 - task_queues:83 processTicksAndRejections node:internal/process/task_queues:83:21 success Checking for changed pages - 0.002s ...

更完整清晰的错误信息图片展示

报错信息展示

解决问题的思路

由于解决这个问题我其实还是走了很多弯路,但是这些弯路在某些发生错误的场景下是必要的,所以我依然会把我走过的弯路和正确的办法都写下来。

分析错误信息

针对此例问题,我们将错误信息拆分成三个重点来看:

  1. ERROR #11321 PLUGIN
  2. Error: Request failed with status code 502
  3. 出错前的最近6条栈信息(- createError.js:16 createError -> - task_queues:83 processTicksAndRejections)

第一个告诉你是插件出错了,随后便指出与 gatsby-source-strapi ;第二个错误看到Request和502,便知和网络有关;第三个追溯错误信息如何产生的。

简单来说就是 gatsby-source-strapi 插件在运行时,发起了网络请求,但是报错了。

同时观察后台的服务,没有任何请求日志输出,如图:

后端无响应

排查一:代码一致性检查

起初,我做了一些小版本升级,也粗略看了升级日志,基本确定没什么影响后,执行 npm outdated ,手动修改依赖版本,进行安全的版本升级后才运行程序,出现了错误,那么最直接的想法就是,单纯的依赖升级可能造成不兼容的问题。这个时候,你有两个选择:

  • 根据上面提到的错误信息去查阅插件升级前后的 CHANGELOG.md ,确保依赖升级后,代码需要进行修改。
  • 还原为初始代码,并确认在代码一致的情况下其他设备能正常运行。(当然也要注意分支也是一致的,这个有时候容易忽略这个问题始终找不到根源)

这个插件我是从 "gatsby-source-strapi": "2.0.0-beta.0" 升级到了 "gatsby-source-strapi": "2.0.0" ,往往从测试版到正式版,说不定会有惊人的变化,所以我就会仔细阅读文档,但实际上并未发现线索。于是保守方案,还原代码。

还原代码的特别说明

有时候项目运行出错,可能跟依赖安装也有关系,比如你使用了第三方npm源安装(根据我的经验,使用taobao的npm源,有概率产生问题,所以我一直使用官方源,但是注意有时候你又需要解决官方源的网络问题)。

依赖版本变更后(理论上 package.json 和对应的 lock 文件是保持同步的),建议删除 node_modules 目录后再进行安装,因为这个目录真的不是单纯的仅用于存放项目依赖,有时候也有缓存(除了 Gatsby.js ,广为人知的 Vite.js ,开发状态下会在 node_modules 内生成 .vite 目录,这里不详细展开)。

其次,安装完依赖,建议检查其他的缓存目录,也许也许要删除,在本例中,可以通过 npm run clean (实际上执行了 gatsby clean ),来彻底清除缓存。

这样就正确的还原了代码部分,好进一步检查是否仍然报错。

PS: 当然最最最干净的还原办法是整个项目删掉,直接 git clone 重新拉,为什么我没有推荐,因为还有一些情况是,部分开发一半的代码(已知和当前问题无关)会丢失,还有更惨的,就是长期没用这个电脑,你不可能还记得你之前有做过 git stash save xxx 的操作,等你运行起代码时,回忆起之前好像写过一些东西暂时stash了,却找不到了,后悔莫及!当然你也可以不删除原项目,将他移动到其他位置或换个名字,这个是可以的或者说再从回收站找回来(我有清空回收站的强迫症)。但既然能做到如此谨慎,用前面几步解决也是很好的。

排查二:环境变更

有时候系统环境、node.js版本、终端等都可能造成影响。由于我印象中升级过macOS、Python、Node.js的版本,所以也需要关注下,不过此类问题表现出来的错误信息都很明显,一眼就能看出来。而在此例的错误信息中表现的情况来看,可以快速排除环境原因。

定位问题及解决

其实本例的错误信息在前面已经表现的很明显了。就是插件在做网络请求出错了。可是以前都是好的,怎么无端端就出错了呢?我不理解,我也想偷懒,于是我们接着来讨论下懒人的解决问题的思路

懒人思路一:面向互联网编程

没错,我暂时无法徒手搞定这个问题,我通常就 Google 一把嗦,把错误信息中的要点用各种组装关键词放谷歌上一搜,我搜索的关键词以本例示例如下:

  1. Request failed with status code 502
  2. "gatsby-source-strapi" threw an error while running the sourceNodes lifecycle:
  3. gatsby-source-strapi 502

简单解释

  1. 第一条是axios通用报错信息,固然找不到线索,就算加上插件名和这段也起不到太大帮助;
  2. 这个就将问题范围缩小了,或许有帮助,可以耐心的翻一下相关帖子;
  3. 这个组合竟然搜不到,大部分结果是40x之类的。

所以 Google 搜索大法失败。

Tips: 往往大部分出错信息,只要提取关键内容去搜索,从搜索结果中找到 StackOverflowGithub 的帖子通常就能解决,英文水平实在不好的,有时候 CSDN博客园 什么的也许会帮到,但是相比效率降低很多。

既然定位到跟插件有关,接下来就是去看插件的github仓库中的Issues,搜索502,什么!一篇也没有。此刻也许你开始怀疑人生了。再去看看文档,仔细看,睁大眼睛多看几遍,仍然没有线索。好罢,这时有点惨。

懒人思路二:检查后端(离谱的弯路)

由于直接关于这个插件的问题,不止出现过一次,仅限本例,由于后端版本升级,某些鉴权的方式也做了调整,印象中之前出现过一些权限问题,但是都会在后端的请求日志中表现出来。我还是有一点点怀疑是不是鉴权的问题直接被拒绝了,又去检查了一下Strapi的GraphQL查询相关的鉴权文档,反复测试,当然没有结果,因为是徒劳,所以也不详细展开。

勤劳致富:手动分析

接下来的解决方法需要调试代码,会涉及到跟本例相关的调试方式,不过你只需要大致了解下思路即可。

看起来还是要耐心调试分析。我用的VSCode,我也擅长node.js调试,建议你也擅长一下:)

基于本例,程序运行的入口是 gatsby-config.js ,插件相关的代码,又要读取环境变量文件,因此,我直接断点在合适的位置(例如下方代码 module.exports 处),如果懒得或者暂时不知道怎么断点,用 console.log 也行吧。

require('dotenv').config({ path: `.env.${process.env.NODE_ENV}`, }); const strapiConfig = { apiURL: process.env.STRAPI_API_URL, accessToken: process.env.STRAPI_TOKEN, collectionTypes: [`post`, `category`, `tag`, `page`], singleTypes: [], headers: { Referer: 'https://www.whidy.net/' } }; module.exports = { siteMetadata: { title: `Whidy Writes`, // ...省略 } }

查看 strapiConfig 的输出内容无误后,进一步分析抛出的错误栈中的提示来进行断点,这里需要点经验。

栈顶是 axios 通用报错处理函数,可以看到如下信息:

[gatsby-blog]/[gatsby-source-strapi]/[axios]/lib/core/createError.js

虽然没有直接展示出 createError.js 路径,但是也比较好推测了,实际上就是 node_modules/gatsby-source-strapi/node_modules/axios/lib/core/createError.js 的第16行:

var error = new Error(message);

那就在这里打个断点吧,运行时的状态调试是更加好追踪的。然而, F5 启动调试,却在 Debug Console 中出现了Process exited with code 1,没有办法执行到这里?!再试试往下一个栈中打断点,多次尝试都失败的话,最直接的方法就直接在源码中通过 console.log 大法完成。

断点调试

我比较熟悉 axios ,所以我直接在 http.js 中添加一段 console.log(config) 输出请求体相关参数。接着 npm run dev ,如图:

axios代码调试

图中请求配置看起来没有任何问题,那就直接用 postman 或者其他工具尝试,因为是 get 请求,Chrome浏览器插件ModHeader都可以测试。如图:

正确的接口返回展示

一切都正常,但是为何在VSCode中就不正常了呢!!!冷静一下,仔细思考。

定位问题:网络代理

从上面手动调试的结果来看,程序代码部分都是正常的,仅在VSCode下的运行报错,而且提示502、后端没有请求记录等现象都证明了在前端部分发出请求时就出问题了。

此时,问题被定位到命令运行时的环境,比如VSCode终端下是否被设置了代理。

Tips: 这里加粗VSCode终端的原因是,VSCode的终端是可配置,不同的终端运行时的设置也许会有差异,我这里是macOS,并设置了 zsh 为默认终端,如果是Windows,有可能是cmd、PowerShell、git bash等等(More Tips,在前几个版本的VSCode更新中,Windows下自带终端按CTRL + C、还要按一次Y的操作可以通过配置VSCode设置解决了),那么多终端那么多可能。

以本例分析,出错的是 zsh ,此时系统运行着 ClashX Pro ,这时,我首先关闭代理工具的,再检查终端的代理设置。

检查npm代理:

npm config get proxy npm config get https-proxy

检查终端的代理:

export http_proxy export https_proxy

很多程序都能设置代理,包括除了上面提到的终端、npm的代理,还有VSCode、git等等,出现了奇怪的网络故障都可以去通过检查代理来排查。如果真的是设置过了,就需要手动清除一下对应的代理。

如果支持curl命令,可以试着在终端运行 curl ipinfo.io 查看返回的数据是否是正常的本地网络。不支持的话,我通常在百度搜索ip,也能快速查看是否代理了。

一切清理工作完成后,再来 npm run dev 试试,果然好了。

问题解决运行状态

其实,就我这个例子来看,我并未设置终端的代理,因为根据我的经验,无论是设置npm还是git等代理后,若是设置了永久代理,就会依赖本地的科学上网服务稳定性,故而最佳实践就是临时设置终端代理,而我似乎就是在升级的时候npm失败了(你懂的国内网络环境经常失败),于是终端下运行了 export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890 安装依赖,结果忘了此事,或许是过了一些时间再运行程序出错了,却想不起来设置过代理了。关闭当前终端重新开一个再运行压根就没有这篇文章了真是囧

总结

折腾了这么久,虽然是我的一个小小的失误造成的,不过本文的重点在于如何排查开发中遇到的问题,提供一个思路。我总结几个重点:

  • 遇事不要慌,尤其是一定要耐心分析错误日志
  • 学习面向 GoogleStackOverflowMDN 等高质量平台寻求答案,学会在 Github Issues 中寻找有帮助的信息
  • 不要嫌麻烦,尝试阅读报错信息中的栈层,进行源码调试。还有些奇怪的问题,一定要试着花一点时间创意一个模拟问题的精简DEMO,避免完整代码中的其他干扰
  • 尽可能避免心存侥幸的各种无意义的瞎蒙式调试,除非这几种瞎蒙的方案真的不怎么费时费力,并且以往确实有过类似经验,否则可能会走真正的弯路
  • 当有多个解决问题的思路时,先在心里评估下哪种解决可能更靠谱更节约时间,就优先尝试

最后,也许确实无法通过上面的方法解决,找身边的同事一起看看也是不错的选择,项目紧急,排期紧张,那请教他人或许才是最优方法,但是如果时间充裕,自己分析研究收货会更多,做好笔记,知识基本就是自己的了。不断沉淀,经验会越来越丰富,离大神更进一步。

avatar

Whidy

一名爱折腾的前端开发工程师,喜欢打篮球和分享 ฅʕ•̫͡•ʔฅ