最新公司的新项目用到了nextjs + koa 做服务端渲染,同时集成了Sentry做异常监控。由于页面的代码可能会在服务端和客户端两个环境下运行,所以我们需要对他们分开监控。
sentry 依赖
sentry 提供了不同的模块来处理浏览器和服务端的监控
- 浏览器 @sentry/browser
- 服务端 @sentry/node
soucemap
nextjs 默认没有生成 soucemap 文件,但是我们可以通过修改 webpack 配置来生成。
1 | // next.config.js |
服务端
捕获
使用 nextjs 做服务端渲染时,我们是通过在页面组件上的静态方法getInitialProps
来初始化页面数据,并通过props
传递给页面组件,从而在服务端完成页面渲染。在这个过程中会有以下几个可能几个异常点
getInitialProps
调用链抛出了异常,如空指针等getInitialProps
存在未捕获的rejection
态- 页面组件在初始化首次
render
过程中抛出的异常
而坑爹的是,nextjs 在内部处理了异常,却没有把错误在服务端层面抛出
1 | // next-server.js源码 |
所幸的是,如果我们在_error.js
自定义错误页的话,可以在getInitialProps
的参数ctx
中得到error
对象。
1 | static getInitialProps({ res, err }: NextPageContext) { |
但是getInitialProps
钩子函数在浏览器端也是会执行的,如在 render 的过程中报错,被 App 的componentDidCatch
捕获后也会渲染 error.js 页面 。为了让客户端和服务端的依赖干净和分离,我更倾向于与在服务端代码中来捕获异常。上面提到了 next 并没有把 react 渲染层的异常抛出来,那我们只能用一些 hack 的方式来处理。
1 | // app是nextApp实例 |
上传到 sentry
sentry 提供了 release 管理功能,即将源码上传到 sentry,这样 sentry 就能将异常的堆栈信息和源码比对,从而定位到保持错误对应的源代码。那么 nextjs 打包的文件在哪呢?
每次打包后,nextjs 会在配置的本地根目录下创建一个.next
文件夹,在.next/server/staic
目录下会有一个由字符串命名的文件夹,这个文件夹的名字则是 nextjs 打包生成的BUILD_ID
,这个文件夹下的pages
文件夹就存放在我们的页面文件,每一个文件都代表一个页面。而这些页面文件都是用于服务端渲染时用的,所以每个文件都会包含所需的全部代码。
所以在打包后,我们需要知道BUILD_ID
才能知道文件所在的目录,而在每次打包后,.next/
目录下会生成一个BUILD_ID
文件,文件的内容就是本次生成的BUILD_ID
。通过读取这个文件便能知道打包文件所在的位置。
由于上传后的文件名只包含了static
之后的路径,所以需要使用命令行的 --url-prefix
参数添加路径前缀,让路径从根目录开始计算。上传的代码大致如下
1 | // 先执行打包,生成 BUILD_ID |
到此为止就可以了吗?并没有。因为当代码部署到服务器后,nodejs 抛出的异常的是文件包含完整的路径的,比如 /user/xxx/xxx/xx/.client/server/static/yyyy/pages/xx.js 这样的路径。而我们上传的文件名是从项目根路径开始计算的,也就是说 sentry 无法判断异常来自哪一个文件。
好在 sentry 提供了RewriteFrames
这个插件,在上传异常前重写错误堆栈中的路径信息。我们将根目录指定成当前项目路径,这个插件就会去掉项目文件夹之前那部分路径,这样一来堆栈中的文件路径就和上传的文件保持一致了。
1 | import { RewriteFrames } from "@sentry/integrations"; |
绿色框框中就是我们源码
客户端
客户端的流程和服务端基本一致,但有如下几点区别
- 客户端 js 代码在
.next/static/chunks
,.next/static/runtime
,.next/static/pages
目录下 - sentry 默认会下根据代码文件底部的
//# sourceMappingURL=_
自动加载 sourcemap,这是最简单的方法。但是这样容易在客户端暴露源码,一般来说我们会不上传 map 文件到服务器或者对 map 文件禁止访问。这种情况下我们需要和服务端代码一样,把打包后的代码和 map 文件上传到 release 的工件库,sentry 也就能正确识别 soucemap 了。 - 如果手动上传 sentry,需要上传上述的三个文件夹
总结
- next 打包
- 通过 BUILD_ID 找到打包的目录
- 上传文件和 sourcemap 到 sentry 并重写路径
- 服务端上传前重写异常的堆栈信息