前端错误监控与上报

187 阅读4分钟

个人博客

Github

最近在写项目的时候,想为项目加上监控,了解关于前端监控的方式

错误监控

try/catch

能捕获常规运行时错误,语法错误和异步错误不行

// 常规运行时错误,可以捕获 ✅
try {
  console.log(notdefined);
} catch(e) {
  console.log('捕获到异常:', e);
}
​
// 语法错误,不能捕获 ❌
try {
  const notdefined,
} catch(e) {
  console.log('捕获到异常:', e);
}
​
// 异步错误,不能捕获 ❌
try {
  setTimeout(() => {
    console.log(notdefined);
  }, 0)
} catch(e) {
  console.log('捕获到异常:',e);
}

总结:

  1. 能捕获包裹体内的同步执行错误。
  2. 不能捕获语法错误。
  3. 不能捕获异步任务错误。
  4. 不能捕获Promise任务错误。
  5. 不能捕获资源加载错误。

window.onerror

js错误收集,window.onerror,当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件

window.onerror = function(message, source, lineno, colno, error) {
   console.log('捕获到异常:', {message, source, lineno, colno, error});
}

先验证下几个错误是否可以捕获

// 常规运行时错误,可以捕获 ✅
window.onerror = function(message, source, lineno, colno, error) {
  console.log('捕获到异常:',{message, source, lineno, colno, error});
}
console.log(notdefined);
​
// 语法错误,不能捕获 ❌
window.onerror = function(message, source, lineno, colno, error) {
  console.log('捕获到异常:',{message, source, lineno, colno, error});
}
const notdefined,
      
// 异步错误,可以捕获 ✅
window.onerror = function(message, source, lineno, colno, error) {
  console.log('捕获到异常:',{message, source, lineno, colno, error});
}
setTimeout(() => {
  console.log(notdefined);
}, 0)
​
// 资源错误,不能捕获 ❌
<script>
  window.onerror = function(message, source, lineno, colno, error) {
  console.log('捕获到异常:',{message, source, lineno, colno, error});
  return true;
}
</script>
<img src="https://abc.cn/image/kkk.png">

需要额外注意:跨域脚本加载错误只有一个“Script error”,并不能获取到错误信息。可以通过在<script>标签上添加“crossorigin”属性来解决这个问题。

总结:

  1. 能捕获所有同步执行错误。
  2. 不能捕获语法错误。
  3. 能捕获普通异步任务错误。
  4. 不能捕获Promise任务错误。
  5. 不能捕获async任务错误。
  6. 不能捕获资源加载错误。

window.addEventListener('error')

当一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,这些 error 事件不会向上冒泡到 window,但能被捕获。而window.onerror不能监测捕获

// 图片、script、css加载错误,都能被捕获 ✅
<script>
  window.addEventListener('error', (error) => {
     console.log('捕获到异常:', error);
  }, true)
</script>
<img src="https://yun.tuia.cn/image/kkk.png">
<script src="https://yun.tuia.cn/foundnull.js"></script>
<link href="https://yun.tuia.cn/foundnull.css" rel="stylesheet"/>
  
// new Image错误,不能捕获 ❌
<script>
  window.addEventListener('error', (error) => {
    console.log('捕获到异常:', error);
  }, true)
</script>
<script>
  new Image().src = 'https://yun.tuia.cn/image/lll.png'
</script>
​
// fetch错误,不能捕获 ❌
<script>
  window.addEventListener('error', (error) => {
    console.log('捕获到异常:', error);
  }, true)
</script>
<script>
  fetch('https://tuia.cn/test')
</script>

相比window.onerror,通过window.addEventListener的方式我们可以捕获资源加载的错误

new Image运用的比较少,可以单独自己处理自己的错误。

但通用的fetch怎么办呢,fetch返回Promise,但Promise的错误不能被捕获,怎么办呢?

window.addEventListener('unhandledrejection')

Promise被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件

// 全局统一处理Promise
window.addEventListener("unhandledrejection", function(e){
  console.log('捕获到异常:', e);
});
fetch('https://tuia.cn/test')

Vue错误

由于Vue会捕获所有Vue单文件组件或者Vue.extend继承的代码,所以在Vue里面出现的错误,并不会直接被window.onerror捕获,而是会抛给Vue.config.errorHandler。

/**
 * 全局捕获Vue错误,直接扔出给onerror处理
 */
Vue.config.errorHandler = function (err) {
  setTimeout(() => {
    throw err
  })
}

React错误

如果渲染的子组件不可信(比如来源于第三方),怎么保证程序的正常运行?

主要通过componentDidCatch,声明一个错误边界的组件

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
​
  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }
​
  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }
​
  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }
​
    return this.props.children; 
  }
}
​
class App extends React.Component {
  render() {
    return (
        <ErrorBoundary>
          <MyWidget />
        </ErrorBoundary>  
    )
  }
}

error boundaries并不会捕捉以下错误:React事件处理,异步代码,error boundaries自己抛出的错误

错误上报

经过上述的异常处理后,我们需要将收集到的错误进行整理(可以通过一些库来解析异常),将需要的信息发送到后台

ajax进行上报

发现错误的时候上传错误到接口进行存储。

但是存在一些问题:

  • 有严格的跨域限制
  • 上报请求可能会阻塞业务
  • 请求容易丢失(被浏览器强制cancel)

image上报

使用 POST 一个 gif 对其进行上报,携带错误信息

var img = new Image();
var errorReportURL = "https://your-server.com/log_error?";
var params = "message=" + encodeURIComponent(errorMessage) + 
    "&url=" + encodeURIComponent(url) + 
    "&line=" + lineNumber + 
    "&column=" + columnNumber + 
    "&error=" + encodeURIComponent(errorObj ? errorObj.stack : '');
​
img.src = errorReportURL + params;

为什么使用 1 x 1 的 gif

原因是:

  1. 没有跨域问题
  2. 发POST 请求之后不需要获取和处理数据、服务器也不需要发送数据
  3. 不会携带当前域名 cookie
  4. 不会阻塞页面加载,影响用户的体验,只需 new Image 对象
  5. 相比于 BMP/PNG 体积最小,可以节约 41% / 35% 的网络资源小

navigator.sendBeacon()

Navigator.sendBeacon() - Web API | MDN (mozilla.org)

navigator.sendBeacon() 方法可用于通过HTTP POST将少量数据异步传输到 Web 服务器。

它主要用于将统计数据发送到 Web 服务器,同时避免了用传统技术(如XMLHttpRequest)发送分析数据的一些问题

详细查看MDN

参考链接

javascript - 一文搞定前端错误捕获和上报 - GrowingIO技术专栏 - SegmentFault 思否

一篇讲透自研的前端错误监控 - 掘金 (juejin.cn)