前端响应式编程 - 0门槛轻松打造前端日志工具快速定位用户端错误

2,817 阅读5分钟

如果你有一年以上的前端开发实战经验, 那么对于下面这种类似的报错截图应该非常熟悉

image.png

image.png

image.png

这些红极一时的图片着实让前端开发者挠破了头皮。

事实上大部分错误都和基础框架无关,但是因为基础框架往往处于整个 JS 堆栈执行的底层,导致业务代码的错误堆栈被掩盖在茫茫多的函数堆栈里。

更别提上线后的混淆的代码了,没有了 sourceMap, 就是佛祖来了都不好使。

而且即使用户端上报的错误日志通过 sourceMap 得到了还原,但因为各种问题,不是代码行数不匹配就是几遍匹配上了也只是回到上面的那种情况,依然被茫茫多的函数堆栈所淹没。

这还是代码同步执行的情况,如果你的错误是异步执行的结果,那你只有去找关公上上香了,因为根本无法定位。

那有没有什么办法可以让用户端的错误日志清晰可读呢?

首先我们先定义这个日志工具的需求

  1. 对业务代码无侵入,能够独立运行
  2. 日志应该清晰可读,可以定向过滤记录内容也可以事后分析。

要打造这样一个日志工具,必然需要一个符合上述条件的底层技术支持,这种技术就是响应式编程技术。

我前面写过几篇文章提到了前端响应式编程的各个方面,先用一个简单的例子来说明,为什么基于响应式编程可以实现这样一个日志工具。

响应式的核心是“事件”

响应式编程是从基于事件的编程模型上发展而来的一种编程技术。和我们所习惯的命令式编程的最大区别在于,响应式编程要求我们将事物抽象成更小的粒度,彼此之间通过事件互相响应来完成一个过程,是去中心化的思维模式,而命令式编程则要求我们先抽象一个能够发布命令控制过程的中心模型,在此基础上构建一整套编程抽象,是一种中心化的思维模式。

去中心化的好处是什么? 是暴富😁, 开玩笑,因为加密货币的去中心化特征促使加密货币的持有者很难被追踪和监管。 而暴富的基础就是破坏现有规则从而谋取暴利,不过咱是本分人,本文不讨论这个。

但是从这种思想上延伸出来,我们可以看到去中心化的编程方式可以避免中心化带来的一些问题。

  1. 随着时间推移过度复杂和臃肿的命令中心。
  2. 代码过度堆积,难以拆分

显然去中心化编程可以大大降低复杂软件的维护成本,例如类似微服务这样的模式,可以让一个复杂的软件应用分解成若干个职责单一的软件应用。

前端发展到现在一直是以一种中心化的编程模式在发展,但其实在复杂的应用维护上也已经遇到了极大的瓶颈,而这些并不是通过智能化和无码化能够解决的,这两者对于维护阶段的成本降低并不太大价值。只能缩短开发周期和降低软件的启动成本。

但学过软件工程的都应该知道,一般正是上线的软件尤其是 B端的软件通常成本最大的部分都是在于软件维护的阶段。

扯远了让我们回到日志工具上,来看看一个简单的例子说明响应式编程如何记录代码的运行过程。

我们先构建一个简单的输入框组件,为了行文方便,我采用 React 来编写

import React from 'react';
import { createComponent } from 'rdeco';

createComponent({
  name: 'input',
  state: {
    value: '',
  },
  controller: {
    onChange(e) {
      this.setter.value(e.target.avlue);
    },
  },
  view: {
    render() {
      return <input type="text" onChange={this.controller.onChange} value={this.state.value} />;
    },
  },
});

这个输入框没什么特别的作用,仅仅是提供了一个可以输入的 Input 组件,我们将其渲染出来。

input.gif

好了重头戏来了,作为最小化日志的一部分,让我们编写一个监听器组件来监听输入的内容

const InputLogger = createComponent({
  name: 'input-logger',
  state: {
    loggerList: ['no log'],
  },
  ref: {
    loggerList: [],
  },
  subscribe: {
    input: {
      state: {
        value({ nextState }) {
          this.ref.loggerList.push(nextState);
        },
      },
    },
  },
  controller: {
    onClick() {
      console.log(this.ref.loggerList);
      this.setter.loggerList(this.ref.loggerList);
    },
  },
  view: {
    render() {
      return (
        <div>
          {this.state.loggerList.map((log) => {
            return <p key={log}>input → state → value :: {log}</p>;
          })}
          <br />
          <button onClick={this.controller.onClick}> Print </button>
        </div>
      );
    },
  },
});

然后将这个监听器渲染到 input 框下面,让我们试试效果

2021-12-06 15.54.34.gif

OK 让我们回顾下,如果将上面这个微小的例子扩大化,如果日志工具能够自动的捕获组件的名称,调用的方法Key 和传递的参数,并且能够足够覆盖到所有的代码。那么就不需要我们关注这些了。

为此我们在 Rdeco 中增加了一个 pluginSubject,将日志工具作为一种插件模式来实现,基于 pluginSubject 你能很容易的捕获到所有响应式对象执行过程中调用的方案,传递的参数,并且这些信息是根据调用的执行顺序来存储的。

我编写了一个简单的日志插件的例子,大致是这样的效果

ezgif.com-gif-maker.gif

这个例子中用户点击登录的时候因为一个 bug 的导致代码挂了,抛出了一个 Error,我在这里手动模拟了一个 loginError 的事件通过这行代码

this.emit('loginError')

在日志中你看到了,最后一行就是 button.event.loginError。

完整的代码例子在这里 codesandbox.io/s/sleepy-de… ,如果你感兴趣可以自己操作一番。

最近我发布了一个新的专栏 前端响应式编程,后续我们会继续发布关于这方面的文章和实战技术,如果你对此感兴趣可以关注我的专栏 juejin.cn/column/7038… , 同时也欢迎关注我们的 git 项目 github.com/kinop112365…