从零开始实现一个错误监控系统

489 阅读3分钟

错误的类型

  • 当JavaScript运行时错误(包括语法错误)发生时,window会触发一个ErrorEvent接口的error事件,并执行window.onerror()
  • 当一项资源(如或处理函数。这些error事件不会向上冒泡到window,不过(至少在Firefox中)能被单一的window.addEventListener (en-US)捕获。
// 摘抄自mdn: https://developer.mozilla.org/zhCN/docs/Web/API/GlobalEventHandlers/onerror
// message错误信息, source: 发生错误的脚本url, lineno: 发生错误的行号
// colno: 发生错误的列号, error: Error对象
window.onerror = function (msg, url, lineNo, columnNo, error) {
    var string = msg.toLowerCase();
    var substring = "script error";
    if (string.indexOf(substring) > -1){
        alert('Script Error: See Browser Console for Detail');
    } else {
        var message = [
            'Message: ' + msg,
            'URL: ' + url,
            'Line: ' + lineNo,
            'Column: ' + columnNo,
            'Error object: ' + JSON.stringify(error)
        ].join(' - ');
​
        alert(message);
    }
    // 该函数返回true,则阻止默认事件处理函数
    return false;
}
​
window.addEventListener('error', function(event) { ... })
​
element.onerror = function(event) {}
                                                  
interface Error {
  message: string; // 错误信息
  name: string; // 错误名称
  stack; // 提供了调用哪些函数、调用顺序、来自哪个行和文件以及使用什么参数的跟踪。堆栈字符串从最近的调用继续到较早的调用,返回到原始的全局范围调用。
  // function@filePath:lineNumber:columnNumber
}                                                  
  • Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件;这可能发生在 window 下,但也可能发生在 Worker 中。 这对于调试回退错误处理非常有用。
// https://developer.mozilla.org/zh-CN/docs/Web/API/Window/unhandledrejection_event
window.addEventListener("unhandledrejection", event => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
​
window.onunhandledrejection = event => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
};
  • react错误

    如果一个 class 组件中定义了 static getDerivedStateFromError()componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。

    错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误

    注意

    错误边界无法捕获以下场景中产生的错误:

    • 事件处理(了解更多
    • 异步代码(例如 setTimeoutrequestAnimationFrame 回调函数)
    • 服务端渲染
    • 它自身抛出来的错误(并非它的子组件)
// https://zh-hans.reactjs.org/docs/error-boundaries.html
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
​
  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }
​
  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    // 这里我用了自己定义的sendReactCatchLog方法上报错误
    // window.Sentry.sendReactCatchLog(error, { componentStack })
    logErrorToMyService(error, errorInfo);
  }
​
  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }
​
    return this.props.children; 
  }
}
​
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>
  • 自定义Error
class CustomError extends Error {
  constructor(foo = 'bar', ...params) {
    super(...params)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, CustomError)
    }
    this.name = 'CustomError'
    this.foo = foo
    this.date = new Date()
  }
}
​
try {
  throw new CustomError('baz', 'bazMessage')
} catch(e) {
  console.error(e.name)    //CustomError
  console.error(e.foo)     //baz
  console.error(e.message) //bazMessage
  console.error(e.stack)   //stacktrace
}
​
// 这里我将会使用window.Sentry.sendCustomErrorLog(error)上传错误
  • 其他

    当加载自不同域的脚本中发生语法错误时,为避免泄露信息(参见错误 363897),语法错误的细节将不会报告

    所以说错误监控系统是不会对跨域错误进行处理的

这里是一个各种情况的错误处理demo

思路流程图

整体流程图

整体流程

错误监控脚本sdk的逻辑流程

整体流程

下面是关键代码实现

\