Le principe de Sentry et comment utiliser les rapports personnalisés

Le dernier article parle de comment connecter le projet front-end à la plateforme de surveillance sentinelle ?

Cet article résume principalement le principe de Sentry et l'utilisation du reporting personnalisé

1. Types d'exceptions courants dans le front-end

  1. Exceptions ECMAScript

developer.mozilla.org/zh-CN/docs/…

  1. DOMException

developer.mozilla.org/zh-CN/docs/…

  1. Erreur de chargement de ressource cdn, img, script, lien, audio, vidéo, iframe...
  • performance.getEntries pour trouver les ressources déchargées
  • Le script ajoute l'attribut crossorigin / le serveur prend en charge le cross-domain
  1. Erreur de promesse

Deuxièmement, la méthode de capture d'exception commune frontale

  1. try catch est synchrone/localement intrusif, ne peut pas gérer les erreurs de syntaxe et async
// 能捕获
try{
    a // 未定义变量
} catch(e){
    console.log(e)
}
// 不能捕获--语法错误
try{
    var a = \ 'a'
}catch(e){
   console.log(e)
}
// 不能捕获--异步
try{
   setTimeout(()=>{
        a
    })
} catch(e){
    console.log(e)
}
// 在setTimeout里面再加一层try catch才会生效
复制代码
  1. window.onerror peut capturer globalement/capturer uniquement les erreurs d'exécution, pas les erreurs de chargement des ressources
// message:错误信息(字符串)。可用于HTML onerror=""处理程序中的event。
// source:发生错误的脚本URL(字符串)
// lineno:发生错误的行号(数字)
// colno:发生错误的列号(数字)
// error:Error对象(对象)
window.onerror = function(message, source, lineno, colno, error) { ... }

// 对于不同域的js文件,window.onerror不能捕获到有效信息。出于安全原因,不同浏览器返回的错误信息参数可能不一致。跨域之后window.onerror在很多浏览器中是无法捕获异常信息的,统一会返回脚本错误(script error)。所以需要对脚本进行设置
// crossorigin="anonymous"
复制代码
  1. window.addEventListener('error') détecte les échecs de chargement des ressources, mais la pile d'erreurs est incomplète
window.addEventListener('error', function(event) { 
    if(!e.message){
        // 网络资源加载错误
    }
}, true)
复制代码
  1. non gérérejet
    1. PromiseLorsqu'il est rejeté et qu'il n'y a pas de gestionnaire de rejet, il déclencheraunhandledrejection
    2. PromiseLorsqu'il est rejeté et qu'il existe un gestionnaire de rejet, il déclencherarejectionhandled
window.addEventListener("unhandledrejection", event => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});
复制代码
  1. setTimeout、setInterval、requestAnimationFrameetc., utilisez l'interception de méthode pour réécrire
const prevSetTimeout = window.setTimeout;

window.setTimeout = function(callback, timeout) {
  const self = this;
  return prevSetTimeout(function() {
    try {
      callback.call(this);
    } catch (e) {
      // 捕获到详细的错误,在这里处理日志上报等了逻辑
      // ...
      throw e;
    }
  }, timeout);
}

复制代码
  1. Vue.config.errorHandler
// sentry中对Vue errorHandler的处理
function vuePlugin(Raven, Vue) {
  var _oldOnError = Vue.config.errorHandler;
  Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
    // 上报
    Raven.captureException(error, {
      extra: metaData
    });
    if (typeof _oldOnError === 'function') {
      _oldOnError.call(this, error, vm, info);
    }
  };
}
module.exports = vuePlugin;
复制代码
  1. React的ErrorBoundary

ErrorBoundaryDéfinition : **Si un composant de classe définit l'une ou l'autre (ou les deux) de ces deux méthodes de cycle de vie, ou ****componentDidCatch()**statique getDerivedStateFromError() . Lorsqu'une erreur est générée, utilisez Render Alternate UI, utilisez Print Error Messagestatic getDerivedStateFromError()componentDidCatch()

// ErrorBoundary的示例
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    this.setState({ hasError: true });
    // 在这里可以做异常的上报
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
}

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

复制代码
  1. demande d'interception
    1. La réécriture XHR intercepte l'envoi et l'ouverture
    2. aller chercher l'interception
    3. axios请求/响应拦截器
  2. 日志拦截 console.XXX 重写
  3. 页面崩溃处理
window.addEventListener('load',()=>{
    sessionStorage.setTitem('page_exit','pending')
})
window.addEventListener('beforeunload',()=>{
    sessionStorage.setTitem('page_exit','true')
})

sessionStorage.getTitem('page_exit')!='true' // 页面崩溃
复制代码

三、错误上报方式

  1. xhr上报
  2. fetch
  3. img
var REPORT_URL = 'xxx'	//数据上报接口
var img = new Image; //创建img标签
img.onload = img.onerror = function(){	//img加载完成或加载src失败时的处理
    img = null;	//img置空,不会循环触发onload/onerror
};
img.src = REPORT_URL + Build._format(params); //数据上报接口地址拼接上报参数作为img的src

复制代码
  1. navigator.sendBeacon

使用 sendBeacon() 方法会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。这就解决了提交分析数据时的所有的问题:数据可靠,传输异步并且不会影响下一页面的加载。

window.addEventListener('unload', logData, false);
function logData() {
    navigator.sendBeacon("/log", analyticsData);
}
复制代码

四、Sentry实现原理

  • Init初始化,配置release和项目dsn等信息,然后将sentry对象挂载到全局对象上。
  • 重写window.onerror方法。

当代码在运行时发生错误时,js会抛出一个Error对象,这个error对象可以通过window.onerror方法获取。Sentry利用TraceKit对window.onerror方法进行了重写,对不同的浏览器差异性进行了统一封装处理。

  • 重写window.onunhandledrejection方法。

因为window.onerror事件无法获取promise未处理的异常,这时需要通过利用window.onunhandledrejection方法进行捕获异常进行上报。在这个方法里根据接收到的错误对象类型进行不同方式的处理。

  1. 如果接收到的是一个ErrorEvent对象,那么直接取出它的error属性即可,这就是对应的error对象。
  2. 如果接收到的是一个DOMError或者DOMException,那么直接解析出name和message即可,因为这类错误通常是使用了已经废弃的DOMAPI导致的,并不会附带上错误堆栈信息。
  3. 如果接收到的是一个标准的错误对象,不做处理
  4. 如果接收到的是一个普通的JavaScript对象
  5. 使用Ajax上传

当前端发生异常时,sentry会采集内容

  • 异常信息:抛出异常的 error 信息,Sentry 会自动捕捉。

  • 用户信息:用户的信息(登录名、id、level 等),所属机构信息,所属公司信息。

  • 行为信息:用户的操作过程,例如登陆后进入 xx 页面,点击 xx 按钮,Sentry 会自动捕捉。

  • 版本信息:运行项目的版本信息(测试、生产、灰度),以及版本号。

  • 设备信息:项目使用的平台,web 项目包括运行设备信息及浏览器版本信息。小程序项目包括运行手机的手机型号、系统版本及微信版本。

  • 时间戳:记录异常发生时间,Sentry 会自动捕捉。

  • 异常等级:Sentry将异常划分等级 "fatal", "error", "warning", "log", "info", "debug", "critical"

  • 平台信息:记录异常发生的项目。

最终会调用Fetch请求上报到对应的Sentry服务器上

五、Sentry配置

注意:如果是Vue项目,请不要在开发环境使用sentry。 因为Vue项目使用sentry时,需要配置@sentry/integrations。而@sentry/integrations是通过自定义Vue的errorHandler hook实现的,这将会停止激活Vue原始logError。 会导致Vue组件中的错误不被打印在控制台中。所以vue项目不要在开发环境使用sentry。

// main.js
// 配置参考
// https://docs.sentry.io/platforms/javascript/guides/vue/configuration/options/
Sentry.init({
  Vue,
  dsn: 'https://[email protected]/4',
  tracesSampleRate: 1.0, // 采样率
  environment: process.env.NODE_ENV, // 环境
  release: process.env.SENTRY_RELEASE, // 版本号
  allowUrls:[], // 是否只匹配url
  initialScope: {}, // 设置初始化数据
  trackComponents: true, // 如果要跟踪子组件的渲染信息
  hooks: ["mount", "update", "destroy"], // 可以知道记录的生命周期
  beforeSend: (event, hint) => event, // 在上报日志前预处理
  // 集成配置
  // 默认集成了
  // InboundFilters
  // FunctionToString
  // TryCatch
  // Breadcrumbs 这里集成了对console日志、dom操作、fetch请求、xhr请求、路由history、sentry上报的日志处理
  // GlobalHandlers 这里拦截了 onerror、onunhandledrejection 事件
  // LinkedErrors 链接报错层级,默认为5
  // UserAgent
  // Dedupe 重复数据删除
  // 修改系统集成  integrations: [new Sentry.Integrations.Breadcrumbs({ console: false })].
  integrations: [
    new BrowserTracing({
      routingInstrumentation: Sentry.vueRouterInstrumentation(router),
      // tracingOrigins: ['localhost', 'my-site-url.com', /^\//],
    }),
  ],
})

复制代码
集成插件SentryRRWeb

sentry还可以录制屏幕的信息,来更快的帮助开发者定位错误官方文档sentry的错误录制其实主要依靠rrweb这个包实现

  • 大概的流程就是首先保存一个一开始完整的dom的快照,然后为每一个节点生成一个唯一的id。
  • 当dom变化的时候通过MutationObserver来监听具体是哪个DOM的哪个属性发生了什么变化,保存起来。
  • 监听页面的鼠标和键盘的交互事件来记录位置和交互信息,最后用来模拟实现用户的操作。
  • 然后通过内部写的解析方法来解析(我理解的这一步是最难的)
  • 通过渲染dom,并用RAF来播放,就好像在看一个视频一样

在beforeSend等hook中添加过滤事件和自定义逻辑
 Sentry.init({
dsn:'https://[email protected]/5706930',
  beforeSend(event, hint) {
  // Check if it is an exception, and if so, show the report dialog
    if (event.exception) {
      Sentry.showReportDialog({ eventId: event.event_id });
    }
    return event;
  }
});

复制代码

6. Rapport manuel Sentry

  1. Définir les propriétés globales
Sentry.setUser({ email: '[email protected]' }) // 设置全局变量
Sentry.configureScope((scope) => scope.clear()); // 清除所有全局变量
Sentry.configureScope((scope) => scope.setUser(null)); // 清除 user 变量

Sentry.setTag("page_locale", "de-at"); // 全局
复制代码
  1. Capture manuelle

Sentry n'intercepte pas automatiquement les exceptions interceptées : si un try/catch est écrit sans relancer l'exception, l'exception n'apparaîtra jamais dans Sentry

import * as Sentry from "@sentry/vue";

// 异常捕获
try {
  aFunctionThatMightFail();
} catch (err) {
  Sentry.captureException(err);
}
// 消息捕获
Sentry.captureMessage("Something went wrong");
// 设置级别
// 级别枚举 ["fatal", "error", "warning", "log", "info", "debug", "critical"]
Sentry.captureMessage("this is a debug message", "debug");
// 设置自定义内容
Sentry.captureMessage("Something went fundamentally wrong", {
  contexts: {
    text: {
      hahah: 22,
    },
  },
  level: Sentry.Severity.Info,
});
// 异常设置级别
Sentry.withScope(function(scope) {
  scope.setLevel("info");
  Sentry.captureException(new Error("custom error"));
});
// 在scope外,继承前面的外置级别
Sentry.captureException(new Error("custom error 2"));

// 设置其他参数 tags, extra, contexts, user, level, fingerprint
// https://docs.sentry.io/platforms/javascript/guides/vue/enriching-events/context/
// Object / Function
Sentry.captureException(new Error("something went wrong"), {
  tags: {
    section: "articles",
  },
});
Sentry.captureException(new Error("clean as never"), scope => {
    scope.clear();
    // 设置用户信息: 
    scope.setUser({ “email”: “[email protected]”})
    // 给事件定义标签: 
    scope.setTags({ ‘api’, ‘api/ list / get’})
    // 设置事件的严重性:
    scope.setLevel(‘error’)
    // 设置事件的分组规则: 
    scope.setFingerprint(['{{ default }}', url])
    // 设置附加数据: 
    scope.setExtra(‘data’, { request: { a: 1, b: 2 })

  return scope;
});

// 事件分组
Sentry.configureScope(scope => scope.setTransactionName("UserListView"));

// 事件自定义 
// 全局
Sentry.configureScope(function(scope) {
  scope.addEventProcessor(function(event, hint) {
    // TODO
    // returning null 会删除这个事件
    return event;
  });
});

// 局部事件
Sentry.withScope(function(scope) {
  scope.addEventProcessor(function(event, hint) {
    // TODO
    // returning null 会删除这个事件
    return event;
  });
  Sentry.captureMessage("Test");
});
复制代码
  1. Rapport d'exception d'interface
// 正常axios sentry是会捕获的
axios.interceptors.response.use(function (response) {
    return response;
  }, function (error) {
    return Promise.reject(error);
  });

// 1. 手动上报网络异常
axios.interceptors.response.use(
  (response: AxiosResponse) => response,
  (error: AxiosError) => {
    Sentry.captureException(error)
    return Promise.reject(error)
  }
)

axios({
  url,
  method,
})
  .then(async (response: AxiosResponse) => {
    resolve(response.data)
  })
  .catch(async (error: AxiosError) => {
    Sentry.captureException(error)
    reject(error)
  })

// 2. 在异常处上报
Sentry.captureException(error, {
  contexts: {
    message: {
      url: error.response.config.baseURL + error.response.config.url,
      data: error.response.config.data,
      method: error.response.config.method,
      status: error.response.status,
      statusText: error.response.statusText,
      responseData: JSON.stringify(error.response.data),
    },
  },
});
复制代码
  1. Gestion des plantages : si le programme se ferme de manière inattendue, il peut être géré dans l'événement de fermeture
Sentry.close(2000).then(function() {
  // perform something after close
});
复制代码

Sept, suivi des performances

  • Heure au-dessus de la ligne de flottaison : l'heure à laquelle la page commence à s'afficher - l'heure à laquelle la demande commence
  • Temps d'écran blanc :responseEnd - navigationStart
  • Temps total de téléchargement de la page :loadEventEnd - navigationStart
  • Résolution DNS fastidieuse :domainLookupEnd - domainLookupStart
  • Temps de connexion TCP :connectEnd - connectStart
  • Prend du temps pour la première demande de paquet :responseEnd - responseStart
  • dom explique le temps:domComplete - domInteractive
  • Temps de fonctionnement de l'utilisateur :domContentLoadedEventEnd - navigationStart

Sentry est principalement implémenté via window.performance

// 收集性能信息
export const getPerformance = () => {
    if (!window.performance) return null;
    const {timing} = window.performance
    if ((getPerformance as any).__performance__) {
        return (getPerformance as any).__performance__;
    }
    const performance = {
        // 重定向耗时
        redirect: timing.redirectEnd - timing.redirectStart,
        // 白屏时间 html head script 执行开始时间
        whiteScreen: window.__STARTTIME__ - timing.navigationStart,
        // DOM 渲染耗时
        dom: timing.domComplete - timing.domLoading,
        // 页面加载耗时
        load: timing.loadEventEnd - timing.navigationStart,
        // 页面卸载耗时
        unload: timing.unloadEventEnd - timing.unloadEventStart,
        // 请求耗时
        request: timing.responseEnd - timing.requestStart,
        // 获取性能信息时当前时间
        time: new Date().getTime(),
    };
    (getPerformance as any).__performance__ = performance;
    return performance;
};
复制代码

Documents de référence

Je suppose que tu aimes

Origine juejin.im/post/7078980068302651400
conseillé
Classement