4

详解JavaScript错误捕获和上报流程

 3 years ago
source link: https://zhuanlan.zhihu.com/p/92914399
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

详解JavaScript错误捕获和上报流程

广发证券 技术工程师

怎么捕获错误并且处理,是一门语言必备的知识。在JavaScript中也是如此。

那怎么捕获错误呢?初看好像很简单,try-catch就可以了嘛!但是有的时候我们发现情况却繁多复杂。

  • Q1: 同步可以try-catch,但一个异步回调,比如setTimeOut里的函数还可以try-catch吗?
  • Q2: Promise的错误捕获怎么做?
  • Q3: async/await怎么捕获错误?
  • Q4: 我能够在全局环境下捕获错误并且处理吗?
  • Q5: React16有什么新的错误捕获方式吗?
  • Q6: 捕获之后怎么上报和处理?

问题有点多,我们一个一个来。

Q1. 同步代码里的错误捕获方式

在同步代码里,我们是最简单的,只要try-catch就完了

function test1 () {
  try {
    throw Error ('callback err');
  } catch (error) {
    console.log ('test1:catch err successfully');
  }
}
test1();

输出结果如下,显然是正常的

v2-8594f59e4053242b354cfaa89fa849d3_720w.jpg

Q2. 普通的异步回调里的错误捕获方式(Promise时代以前)

上面的问题来了,我们还能通过直接的try-catch在异步回调外部捕获错误吗?我们试一试

// 尝试在异步回调外部捕获错误的结果
function test2 () {
  try {
    setTimeout (function () {
      throw Error ('callback err');
    });
  } catch (error) {
    console.log ('test2:catch err successfully');
  }
}
test2();

注意这里的Uncaught Error的文本,它告诉我们错误没有被成功捕捉。

为什么呢? 因为try-catch的是属于同步代码,它执行的时候,setTimeOut内部的的匿名函数还没有执行呢。而内部的那个匿名函数执行的时候,try-catch早就执行完了。

( error的内心想法:哈哈,只要我跑的够慢,try-catch还是追不上我!)

但是我们简单想一想,诶我们把try-catch写到函数里面不就完事了嘛!

function test2_1 () {
  setTimeout (function () {
    try {
      throw Error ('callback err');
    } catch (error) {
      console.log ('test2_1:catch err successfully');
    }
  });
}
test2_1();

输出结果如下,告诉我们这方法可行

总结下Promise时代以前,异步回调中捕获和处理错误的方法

  • 在异步回调内部编写try-catch去捕获和处理,不要在外部哦
  • 很多异步操作会开放error事件,我们根据事件去操作就可以了

Q3. Promise里的错误捕获方式

可通过Promise.catch方法捕获

function test3 () {
  new Promise ((resolve, reject) => {
    throw Error ('promise error');
  }).catch (err => {
    console.log ('promise error');
  });
}

输出结果

>> reject方法调用和throw Error都可以通过Promise.catch方法捕获

function test4 () {
  new Promise ((resolve, reject) => {
    reject ('promise reject error');
  }).catch (err => {
    console.log (err);
  });
}

>> then方法中的失败回调和Promise.catch的关系

  • 如果前面的then方法没写失败回调,失败时后面的catch是会被调用的
  • 如果前面的then方法写了失败回调,又没抛出,那么后面的catch就不会被调用了
// then方法没写失败回调
function test5 () {
  new Promise ((resolve, reject) => {
    throw Error ('promise error');
  })
    .then (success => {})
    .catch (err => {
      console.log ('the error has not been swallowed up');
    });
}
// then方法写了失败回调
function test5 () {
  new Promise ((resolve, reject) => {
    throw Error ('promise error');
  })
    .then (success => {},err => {})
    .catch (err => {
      console.log ('the error has not been swallowed up');
    });
}

输出分别为

1.the error has not been swallowed up
2.无输出

Q4.async/await里的错误捕获方式

对于async/await这种类型的异步,我们可以通过try-catch去解决

async function test6 () {
  try {
    await getErrorP ();
  } catch (error) {
    console.log ('async/await error with throw error');
  }
}

function getErrorP () {
  return new Promise ((resolve, reject) => {
    throw Error ('promise error');
  });
}
test6();

输出结果如下

>> 如果被await修饰的Promise因为reject调用而变化,它也是能被try-catch的

(我已经证明了这一点,但是这里位置不够,我写不下了)

Q5.在全局环境下如何监听错误

window.onerror可以监听全局错误,但是很显然错误还是会抛出

window.onerror = function (err) {
  console.log ('global error');
};
throw Error ('global error');

Q6.在React16以上如何监听错误

>> componentDidCatch和getDerivedStateFromError钩子函数

window.onerror = function (err) {
  console.log ('global error');
};
throw Error ('global error');

有错误,那肯定要上报啊!不上报就发现不了Bug这个样子。Sentry这位老哥就是个人才,日志记录又好看,每次见面就像回家一样

Sentry简单介绍

Sentry provides open-source and hosted error monitoring that helps all software
teams discover, triage, and prioritize errors in real-time.
One million developers at over fifty thousand companies already ship
better software faster with Sentry. Won’t you join them?
—— Sentry官网

Sentry是一个日志上报系统,Sentry 是一个实时的日志记录和汇总处理的平台。专注于错误监控,发现和数据处理,可以让我们不再依赖于用户反馈才能发现和解决线上bug。让我们简单看一下Sentry支持哪些语言和平台吧

在JavaScript领域,Sentry的支持也可以说是面面俱到

参考链接
https://docs.sentry.io/platforms/

Sentry的功能简单说就是,你在代码中catch错误,然后调用Sentry的方法,然后Sentry就会自动帮你分析和整理错误日志,例如下面这张图截取自Sentry的网站中

在JavaScript中使用Sentry

1.首先呢,你当然要注册Sentry的账号

这个时候Sentry会自动给你分配一个唯一标示,这个标示在Sentry里叫做 dsn

2. 安卓模块并get start

安装@sentry/browser
npm install @sentry/browser

在项目中初始化并使用

import * as Sentry from '@sentry/browser';

Sentry.init ({
  dsn: 'xxxx',
});

try {
 throw Error ('我是一个error');
} catch (err) {
 // 捕捉错误
  Sentry.captureException (err);
}

3.给Sentry配置sourceMap看源码

在Sentry中默认看到的是编译后的有错代码,如果想看源码需要上传sourceMap

// 安装
$ npm install --save-dev @sentry/webpack-plugin
$ yarn add --dev @sentry/webpack-plugin


// 配置webpack
const SentryWebpackPlugin = require('@sentry/webpack-plugin');
module.exports = {
 // other configuration
  plugins: [
 new SentryWebpackPlugin({
      include: '.',
      ignoreFile: '.sentrycliignore',
      ignore: ['node_modules', 'webpack.config.js'],
      configFile: 'sentry.properties'
 })
 ]
};

4. 为什么不是raven.js?

// 下面写法已经废弃,虽然你还是可以用
var Raven = require('raven-js');
Raven
 .config('xxxxxxxxxxx_dsn')
 .install();

Sentry的核心功能总结

>> 捕获错误

try {
  aFunctionThatMightFail();
} catch (err) {
  Sentry.captureException(err);
}

>> 设置该错误发生的用户信息

下面每个选项都是可选的,但必须 存在一个选项 才能使Sentry SDK捕获用户: id

Sentry.setUser({
  id:"penghuwan12314"
  email: "[email protected]",
  username:"penghuwan",
  ip_addressZ:'xxx.xxx.xxx.xxx'
});

>> 设置额外数据

Sentry.setExtra("character_name", "Mighty Fighter");

>> 设置作用域

Sentry.withScope(function(scope) {
  // 下面的set的效果只存在于函数的作用域内
  scope.setFingerprint(['Database Connection Error']);
  scope.setUser(someUser);
  Sentry.captureException(err);
});
// 在这里,上面的setUser的设置效果会消失

>> 设置错误的分组

整理日志信息,避免过度冗余

Sentry.configureScope(function(scope) {
  scope.setFingerprint(['my-view-function']);
});

>> 设置错误的级别

在阅读日志时可以确定各个bug的紧急度,确定排查的优先书序

Sentry.captureMessage('this is a debug message', 'debug');
//fatal,error,warning,info,debug五个值
// fatal最严重,debug最轻

>> 自动记录某些事件

例如下面的方法,会在每次屏幕调整时完成上报

window.addEventListener('resize', function(event){
  Sentry.addBreadcrumb({
    category: 'ui',
    message: 'New window size:' + window.innerWidth + 'x' + window.innerHeight,
    level: 'info'
 });
})

Sentry实践的运用

可以根据环境设置不同的dsn

let dsn;
  if (env === 'test') {
    dsn = '测试环境的dsn';
  } else {
    dsn =
      '正式环境的dsn';
  }

Sentry.init ({
  dsn
});

其他想到了再更,完


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK