Sentry & console.log

JavaScript

Sentry 是 JavaScript 项目中非常常见的错误监控模块,一般会在 production 环境开启,且在 Sentry 加载之后会对环境中的很多 API 进行改写(比如改写 console.log API)。

这导致了一个潜在的风险,就是 production 环境和 development 环境进行 console.log 调用的“成本”是不同的。

在 Sentry v4.x 版本中,如果试图使用 console.log 输出一个 React FiberNode,很可能会造成线上代码无响应,最终触发程序 Out of Memory 的报错。

造成这一问题的原因是:

当使用 Sentry 包装过的 console.log API 进行 FiberNode 打印时,Sentry 会进行“增加面包屑”的步骤(见 @sentry/browser/src/integrations/breadcrumbs.ts)。在这一步骤中,Sentry 会试图将当前这次 console.log 调用的信息记录下来,包括调用的 API 名称、调用的参数等等。为了更好的保存数据,Sentry 会将这次的调用数据进行处理,并存储成可以方便网络传输的格式(调用的过程从 @sentry/hub/src/scope.ts@sentry/utils/src/object.ts)。在处理的过程中,Sentry 会试图去除当前 Object 潜在的循环引用,以方便 JSON 进行序列化操作(见 decycle 函数)。

decycle 的代码不难了解,Sentry 使用的是深度优先遍历搜索,遍历整个对象上的各个字段,并将所有访问过的字段都存储到 memo 中。如果访问到一个已经存在于 memo 中的字段,就认为出现了循环引用,这时候通过返回 '[Circular ~]' 字符串而不是真是的对象,来删去这个循环引用的节点。

而问题就出在这里。每一个 React FiberNode 上本身就存储了大量的信息(比如 memoizedPropsmemoizedStatetypechild 等),同时双向链表的数据结构让 FiberNode 的节点可以非常深。这使得 Sentry 分析整个对象需要花费大量的计算成本,也需要记录大量已经访问过的节点。最终呈现出来的现象就是线上程序的卡死,以及 Out of Memory 的报错。

总结来说一句话:线上程序,不要输出 console.log