前端项目异常监控-全局捕获Promise错误

1.核心

全局监听unhandledrejection,该事件为Promise被reject时但没有reject处理器时(没有被catch处理),则触发该事件。( async 函数内部的异步任务一旦出现错误,那么就等同于 async 函数返回的 Promise 对象被 reject。)

2.编写辅助函数

 2.1getLastEvent获取最后一个事件

复制代码
let lastEvent;

['click', 'touchstart', 'mousedown', 'keydown', 'mouseover'].forEach(
  (eventType) => {
    document.addEventListener(
      eventType,
      (event) => {
        lastEvent = event
      },
      {
        capture: true, // 是在捕获阶段还是冒泡阶段执行
        passive: true // 默认不阻止默认事件
      }
    )
  }
)

export default function () {
  return lastEvent
}
复制代码

 2.2getSelector获取操作元素

复制代码
function getSelectors(path) {
  // 反转 + 过滤 + 映射 + 拼接
  return path
    .reverse()
    .filter((element) => {
      return element !== document && element !== window
    })
    .map((element) => {
      console.log('element', element.nodeName)
      let selector = ''
      if (element.id) {
        return `${element.nodeName.toLowerCase()}#${element.id}`
      } else if (element.className && typeof element.className === 'string') {
        return `${element.nodeName.toLowerCase()}.${element.className}`
      } else {
        selector = element.nodeName.toLowerCase()
      }
      return selector
    })
    .join(' ')
}

export default function (pathsOrTarget) {
  if (Array.isArray(pathsOrTarget)) {
    return getSelectors(pathsOrTarget)
  } else {
    let path = []
    while (pathsOrTarget) {
      path.push(pathsOrTarget)
      pathsOrTarget = pathsOrTarget.parentNode
    }
    return getSelectors(path)
  }
}
复制代码

 2.3tracker报错处理器,针对捕获到的错误后进行的操作统一处理

复制代码
// 主机
// let host = 'cn-guangdong-log.aliyuncs.com'
// 项目名
// let project = 'yymonitor'
// 存储名
// let logstore = 'yymonitor-store'
let userAgent = require('user-agent')

function getExtraData() {
  return {
    title: document.title,
    url: location.href,
    timestamp: Date.now(),
    userAgent: userAgent.parse(navigator.userAgent).name
  }
}

class SendTracker {
  // constructor() {
  //   // 上报的路径
  //   this.url = `http://${project}.${host}/logstores/${logstore}/track`
  //   this.xhr = new XMLHttpRequest()
  // }
  send(data = {}) {
    let extraData = getExtraData()
    let log = { ...data, ...extraData }
    // 阿里云要求值不能为数字
    for (const key in log) {
      if (typeof log[key] === 'number') {
        log[key] = `${log[key]}`
      }
    }
    console.log('log', log)
    // 接入日志系统,此处以阿里云为例
    // let body = JSON.stringify({
    //   __logs__: [log]
    // })
    // this.xhr.open('POST', this.url, true)
    // this.xhr.setRequestHeader('Content-Type', 'application/json')
    // this.xhr.setRequestHeader('x-log-apiversion', '1.0.0')
    // this.xhr.setRequestHeader('x-log-bodyrawsize', body.length)
    // this.xhr.onload = function () {
    //   // console.log(this.xhr.response);
    // }
    // this.xhr.onerror = function (error) {
    //   console.log(error)
    // }
    // this.xhr.send(body)
  }
}

export default new SendTracker()
复制代码

3.编写全局监听函数,附带捕获JS资源加载异常

复制代码
import getLastEvent from '../utils/getLastEvent'
import getSelector from '../utils/getSelector'
import tracker from '../utils/tracker'

export function injectJsError() {
  // 监听全局未捕获的错误
  window.addEventListener(
    'error',
    (event) => {
      console.log('error+++++++++++', event)
      let lastEvent = getLastEvent() // 获取到最后一个交互事件
      // 脚本加载错误
      if (event.target && (event.target.src || event.target.href)) {
        tracker.send({
          kind: 'stability', // 监控指标的大类,稳定性
          type: 'error', // 小类型,这是一个错误
          errorType: 'resourceError', // js执行错误
          filename: event.target.src || event.target.href, // 哪个文件报错了
          tagName: event.target.tagName,
          selector: getSelector(event.target) // 代表最后一个操作的元素
        })
      } else {
        tracker.send({
          kind: 'stability', // 监控指标的大类,稳定性
          type: 'error', // 小类型,这是一个错误
          errorType: 'jsError', // js执行错误
          message: event.message, // 报错信息
          filename: event.filename, // 哪个文件报错了
          position: `${event.lineno}:${event.colno}`, // 报错的行列位置
          stack: getLines(event.error.stack),
          selector: lastEvent ? getSelector(lastEvent.path) : '' // 代表最后一个操作的元素
        })
      }
    },
    true
  )

  window.addEventListener(
    'unhandledrejection',
    (event) => {
      console.log('unhandledrejection-------- ', event)
      let lastEvent = getLastEvent() // 获取到最后一个交互事件
      let message
      let filename
      let line = 0
      let column = 0
      let stack = ''
      let reason = event.reason
      if (typeof reason === 'string') {
        message = reason
      } else if (typeof reason === 'object') {
        message = reason.message
        if (reason.stack) {
          let matchResult = reason.stack.match(/at\s+(.+):(\d+):(\d+)/)
          filename = matchResult[1]
          line = matchResult[2]
          column = matchResult[3]
        }
        stack = getLines(reason.stack)
      }
      tracker.send({
        kind: 'stability', // 监控指标的大类,稳定性
        type: 'error', // 小类型,这是一个错误
        errorType: 'promiseError', // js执行错误
        message, // 报错信息
        filename, // 哪个文件报错了
        position: `${line}:${column}`, // 报错的行列位置
        stack,
        selector: lastEvent ? getSelector(lastEvent.path) : '' // 代表最后一个操作的元素
      })
    },
    true
  )
}

function getLines(stack) {
  return stack
    .split('\n')
    .slice(1)
    .map((item) => item.replace(/^\s+at\s+/g, ''))
    .join('^')
}
复制代码

 

posted on   ChoZ  阅读(934)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示