热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

前端异常捕获且日志上报处理

前端异常捕获且日志上报处理-JavaScript网页异常捕获一、异常大概分类一般我们想要捕获的异常大概分类:语法错误onerror事件代码块与语法错误代码块不在一起,
Javascript 网页异常捕获

一、异常大概分类

一般我们想要捕获的异常大概分类:

  • 语法错误

    • onerror 事件代码块与语法错误代码块不在一起,例如trycatche
    • 或者同在一个代码块,但是语法错误代码块异步执行
      以上情况都可以用onerror捕获语法错误
      setTimeout(() => { 
          eval('function()') 
      }, 1000); 
      // Uncaught SyntaxError: Function statements require a function name
  • 引用错误,类型错误,uri 错误,范围错误等等

    非try catch包裹情况下,可以使用 onerror 捕获同步错误、异步错误

      console.log(a) // Uncaught ReferenceError: a is not defined 
      Array.test() // 调用了 Array 上不存在的 test,值为 undefined,作为函数执行,则会抛出类型错误 // Uncaught TypeError: Array.test is not a function 
      new Array(12221312312) // Uncaught RangeError: Invalid array length
      decodeURI('%') // Uncaught URIError: URI malformed
  • try{} catch{}

    若 try 代码块报错,只能在 catch 中捕获。但是 try 代码块中若有异步错误代码,catch 无法捕获,会被 onerror 捕获

      try { 
          setTimeout(() => { 
              console.log('a', a) // 可以被 onerror 捕获 
          }, 1000) } 
      catch(e) { console.log('e', e) } // Uncaught ReferenceError: a is not defined
  • Promise 抛出错误

    • 没有设置 catch 捕获
      let p = new Promise((resolve, reject) => { 
        reject(1) }
      ) // 这里没有做 catch 处理 
      // Uncaught (in promise) 1
    • 在 catch 中报错没有捕获
      ;(async function xx() { 
        try { throw 1 } 
        catch(e) { 
            console.log('a', a) // 这里出错可以使用 unhandledrejection 来捕获 
        } 
      })() 
      // Uncaught ReferenceError: a is not defined

      上面两种情况可以监听 unhandledrejection 捕获错误

      window.addEventListener('unhandledrejection', function(e) { 
        // e.preventDefault(); // 阻止异常向上抛出 
        console.log('捕获到异常 unhandledrejection :', e) 
      })
  • 静态资源加载失败

    • 在资源上添加 onerror 事件
      // html 
      //  
      // js 
      let imgID = document.getElementById('imgID') 
      imgID.Onerror= function(e) { 
        console.log('img load error :>> ', e); 
      } // 注意:onerror 需要定义之后,再设置图片路径,才能捕获到加载失败 
      imgID.src = 'http://xxx.png'
    • 静态资源网络请求失败事件不会冒泡,需要在捕获阶段捕获
      chrome、FF 中可以通过冒泡方式监听 error 事件捕获资源加载失败
      // 注意:此处会与上面的 onerror 事件一起触发,将导致日志重复上报
      // 可以只使用 addEventListener 捕获模式统一监听, 就不需要注册 window.onerror 了
      window.addEventListener(
        'error',
        error => {
            console.log('addEventListener 捕获到异常:', error)
        },
        true
      )
  • 网页崩溃

    • 网页加载后,埋入一个标志,表示正在加载
      // 初次进来,将埋入一个标志,值为 pending ,正常退出后,会设置为 true 
      // 若网页崩溃,第二次回来页面后,读取当前标志,如果值存在且为 true 则表示正常退出 
      // 如果不是 true ,则表示上次可能是崩溃了,需要上报之前定时更新的时间值 
      if(localStorage.getItem('good_exit') && localStorage.getItem('good_exit') !== 'true') { 
        localStorage.getItem('time_before_crash') // 日志上报,将崩溃之前的时间一起上报 
      } 
      window.addEventListener('load', function () { 
        localStorage.setItem('good_exit', 'pending'); // 定时更新崩溃之前的网页时间 
        setInterval(function () { 
            localStorage.setItem('time_before_crash', new Date().toString()); 
        }, 10000); 
      }) 
      window.addEventListener('beforeunload', 
        function () { // 网页正常退出后,将埋入标志,设置成 true,表示正常退出 
        localStorage.setItem('good_exit', 'true'); 
      })

      上面是在 第二次 进入页面才知道网页崩溃,那么有什么方法可以在网页崩溃之后就可以上报呢

    • 可以使用 Service Worker 来进行监控
      其生命周期与页面无关(关联页面未关闭时,它也可以退出,没有关联页面时,它也可以启动)
      1、注册 service worker js 
      2、 每间隔 10 秒,就向 worker 发送消息 消息中包含: 
      { 
        type 字段:active 表示正常活跃,exit 表示正常退出 
        time 字段:表示当前时间 
      } 
      在页面要退出后,发送 type: exit,表示正常退出 
      3、 worker 内部注册有消息接收事件,接收页面发送过来的消息 
      接收到 type 为 active,表示正常活跃,更新内部 time 的值 
      接收到 type 为 exit,表示正常退出,更新内部 time 值为 0 
      worker 内部维护一个状态对象,包含时间,值为页面发送过来 
      每隔 15 秒检查一次,若时间与上一次没有改变,则说明页面可能崩溃(注意区分 time 为 0 的情况)
  • Script Error

    跨域脚本的错误信息,因为处于保护信息的原因,只会展示 Script Error,
    通过以下方式解决

      1、外链脚本增加 crossorigin 属性: 
      2、同时脚本的 Access-Control-Allow-Origin: 设置为 * 或者 当前域名
  • iframe Error

      
      
  • vue 自身 try catch 处理了错误,导致我们无法捕获

    使用 Vue 提供的 errorHandler 方法捕获

      Vue.config.errorHandler = function(err, vm, info) { 
          let { 
          message, // 异常信息 
          name, // 异常名称 
          script, // 异常脚本url 
          line, // 异常行号 
          column, // 异常列号 
          stack // 异常堆栈信息 } = err 
          // vm 为抛出异常的 Vue 实例 
          // info 为 Vue 特定的错误信息,比如错误所在的生命周期钩子 
          console.log('vue err,vm,info :>> ', err, vm, info) 
      }

    所以捕获错误总结下来:

    // 1、try catch 中的 catch 错误捕获 
    可以在 webpack 打包时候,使用 AST 方式解析并在 catch 中插入日志上报代码
    // 2、error 事件
    window.addEventListener( 'error', error => { 
      let data = {} // 此处与上面的 onerror 会重复事件 
      let { colno, lineno, message, filename, error, stack } = error //不一定所有浏览器都支持 colno 参数 
      let col = colno || (window.event && window.event.errorCharacter) || 0 
      data.url = url 
      data.line = line 
      data.col = col 
      if (!!stack){ 
          //如果浏览器有堆栈信息 
          //直接使用 
          data.msg = stack.toString() 
      }else if (!!arguments.callee){ 
          //尝试通过callee拿堆栈信息 
          let ext = [] 
          let f = arguments.callee.caller, c = 3 //这里只拿三层堆栈信息 
          while (f && (--c>0)) { 
              ext.push(f.toString()) 
              if (f === f.caller) { 
                  break //如果有环 
              } 
              f = f.caller 
          } 
          ext = ext.join(",") data.msg = ext 
      } 
      console.log('3、addEventListener error 捕获阶段>>>异常:', data) 
    }, true )
    // 3、unhandledrejection 
    window.addEventListener('unhandledrejection', function(e) { 
      // e.preventDefault(); 
      // 阻止异常向上抛出 
      console.log('4、promise 异常 unhandledrejection :', e) 
    })
    // 4、errorHandler 
    Vue.config.errorHandler = function(err, vm, info) { 
    // vm 为抛出异常的 Vue 实例 
    // info 为 Vue 特定的错误信息,比如错误所在的生命周期钩子 
    let { 
      message, // 异常信息 
      name, // 异常名称 
      script, // 异常脚本url 
      line, // 异常行号 
      column, // 异常列号
      stack // 异常堆栈信息 
    } = err 
    console.log('2、vue errorHandler :>> ', err, vm, info) }

    二、错误日志上报

    既然异常已经捕获到了,那我们怎么处理呢,如何上报,需要上报哪些内容?

    日志分类

    1、一般日志分类等级

log、debug、info、warn、error

2、分场景使用日志上报类型

log: 记录流程信息 
debug: 记录调试关键信息 
info: 记录业务功能点,是否触发成功或者失败
warn: 页面警告信息 
error: 页面错误或者业务异常信息

3、日志上报信息附带信息

1、用户id、session、用户名 
2、当前错误信息 
3、可以用来重现、推断当前错误发生的信息 
4、上报时间 
5、日志等级等等

4、日志上报策略

1、达量上传,设置一个缓存数量,到达即上报。因为不能一发生错误就要上报,会影响用户的网络。 2、日志埋点处各自有各自的上报等级。 需要有一个总配置地方,配置当前的上报等级 这样各处埋点可以判断当前需要上报的日志等级,等级小于设置值的话,不可上报。 
3、本地缓存若暂存过多,需要删除前面的数据 
4、抽样上报 
5、设置缓存有效时间 等等

上报之后,接下来的步骤就是在服务端收集分析归类展示,基于badjs我们搭建一整套日志解析系统

三、日志上报

badjs 服务安装

1、前期预备工作

为了快速搭建,我们统一使用 docker 安装

备注:windows 环境使用 docker,需要安装 Docker Desktop

  • mysql 安装
    docker 安装 mysql

    备注:mysql 安装好后,需要从 badjs-web (需要先把项目下载下来)项目中的 db 目录下。
    使用 create.sql 初始化 web 相关的数据库

  • mongodb 安装(不可设置密码)
    docker 安装 mongo
    2、项目安装

github 克隆项目到本地

git clone https://github.com/BetterJS/badjs-installer

子项目下载以及依赖安装

// 克隆下载 badjs-acceptor、badjs-mq、badjs-storage、badjs-web 项目 
yarn clone 
// 安装各项目的依赖 
yarn install

3、修改配置项

  • 修改 badjs-acceptor 项目的 project.debug.json/project.json

    注意:这里是日志上报的地方,客户端初始化 badjs-report 时候需要设置的 url 属性即是这里的服务地址

    http://{badjs-acceptor:port}/badjs

      // 修改 port 属性: 从 80 改为 8083; 
      // 因为 node 默认没有 80 端口的权限,需要你使用管理员权限才可以使用 
      { "port": 8083 }
  • 修改 badjs-web 项目的 project.debug.json/project.json

      // 修改 mysql 属性,配置我们 docker 安装好的 mysql 用户密码与端口
      { 
          "mysql" : { 
              "url" : "mysql://root:123456@localhost:3306/badjs" 
          } 
      }

    4、启动项目

yarn start

查看 badjs-web 的启动端口,访问 http://localhost:port 可以看到日志后台管理服务页面

四、badjs各模块

1、badjs-acceptor 接受客户端上报的日志

badjs-acceptor 收到日志上报,发送到 badjs-mq
package.json 中配置 dispatcher 分发属性, 表示向 badjs-mq 请求的信息:如请求端口(10001)

2、badjs-mq 消息队列,保证消息有序稳定被接受

badjs-mq 接收 badjs-acceptor 的请求
package.json 中配置 acceptor 接收属性, 表示用来接收信息所配置的接口信息:如端口(10001)。

badjs-mq 再分发到 badjs-storage
package.json 中配置 dispatcher 分发属性, 表示向 badjs-storage 请求的信息:如端口(10000)

3、badjs-storage 存储模块

badjs-storage: 接收来自 badjs-mq 的请求,再写入到 mongodb
package.json 中配置 acceptor 接收属性, 表示用来接收信息所配置的接口信息:如端口(10000)

4、badjs-web 日志后台管理系统

badjs-web 查询日志存储,分类查看日志信息,解析日志内容
package.json 中配置 acceptor 接收属性, badjs-acceptor 可请求的端口
package.json 中配置 storage 存储属性, 查询 mongodb 数据
package.json 中配置 mysql 数据库属性, 查询 mysql 数据等等

五、上报日志插件 badjs-report

badjs-report 重写了 window.onerror 来捕获错误

1、安装

yarn add badjs-report

2、初始化

import badjs from 'badjs-report'

badjs.init({
    // 必须配置项
    id: 1 // 此 id 为 badjs-web 启动后,申请的项目的 id,上报的日志根据该 id 区分业务模块
    url: 'http://badjs-acceptor启动后的地址', // 日志上报到的地方

    // 选择配置
    uin: 123, // 指定用户的 id (该插件默认读取 qq uin)
    delay: 1000, // 延迟多少毫秒,合并缓冲区中的上报(默认 1000)
    ignore: [/Script error/i], // 忽略某个错误,遇到该错误不进行上报
    random: 1, // 抽样上报, 值可以设置 0-1 之间。1 表示 100% 上报(默认为 1)
    repeat: 5, // 重复上报次数(对于同一个错误超过多少次不上报;避免单个用户同一错误上报过多的情况)
    onReport: function(id, errObj) {}, // 上报日志之后的回调。id 为上报的 id,errorObj 为上报的错误对象
    submit: function(url) {}, // 覆盖原来的上报方式,原来是使用 new Image() 形式上报,可以修改成自己想要上报的方式,比如使用 post 内部构造好的 url
    ext: {}, // 扩展属性,后端做扩展处理属性。设置了 ext 的值,就会作为 'ext=设置的值' 合并到构造好的上报 url 中
    offlineLog: false, // 是否开启离线日志(默认不开启为 false)
    offlineLogExp: 5 // 离线有效时间(默认最近5天)
})

3、手动上报

a、badjs.report('error msg')
b、badjs.report({
    msg: 'error msg', // 需要上报的错误信息
    target: 'error.js', // 发生错误的 js 文件
    rowNum: 1, // 发生错误的行数
    colNum: 2 // 发生错误的列数
})

4、延迟上报

暂存

badjs.push('error msg')
badjs.push({
    msg: 'error msg', // 需要上报的错误信息
    target: 'error.js', // 发生错误的 js 文件
    rowNum: 1, // 发生错误的行数
    colNum: 2 // 发生错误的列数
})

立即上报

badjs.report({
    msg: 'error msg', // 需要上报的错误信息
    target: 'error.js', // 发生错误的 js 文件
    rowNum: 1, // 发生错误的行数
    colNum: 2 // 发生错误的列数
})

5、上报离线日志

badjs.reportOfflineLog()

六、项目应用

import BJ_REPORT from 'badjs-report'
import Vue from 'vue'

// 环境ID枚举
let ENV_ID_ENUM = {
    DEV: 1,
    SIT: 2,
    UAT: 3,
    PRO: 4
}
let curEnv = '',
    origin = window.location.origin
if (origin.indexOf('dev.xxx.com') > -1) {
    // http://dev.xxx.com
    // DEV 环境
    curEnv = 'DEV'
} else if (origin.indexOf('sit.xxx.com') > -1) {
    // http://sit.xxx.com
    // SIT 环境
    curEnv = 'SIT'
} else if (origin.indexOf('uat.xxx.com') > -1) {
    // http://uat.xxx.com
    // UAT 环境
    curEnv = 'UAT'
} else if (origin.indexOf('m.xxx.com') > -1) {
    // http://pro.xxx.com
    // PRO 环境
    curEnv = 'PRO'
}
let envID = ENV_ID_ENUM[curEnv] || ENV_ID_ENUM.DEV

// 初始化日志上报插件
BJ_REPORT.init({
    id: envID, // 不指定 id 将不上报,
    url: 'http://{badjs-acceptor:port}/badjs'
})

// 初始化项目时,可以暴露一个全局的 vm 实例,方便上传需要的信息
window.rootvm = new Vue({}) // 初始化项目

// 初始化监听异常
init(window.rootvm)
function init(rootInstance) {
    Vue.config.errorHandler = function(err, curInstance, info) {
        // vm 为抛出异常的 Vue 实例
        // info 为 Vue 特定的错误信息,比如错误所在的生命周期钩子
        let {
            message, // 异常信息
            // name, // 异常名称
            // script, // 异常脚本url
            line, // 异常行号
            column, // 异常列号
            stack // 异常堆栈信息
        } = err
        log(message, stack, line, column, curInstance)
        console.log('vue errorHandler :>> ', err, curInstance, info)
    }
    window.addEventListener(
        'error',
        e => {
            let { colno, lineno, message, filename } = e
            log(message, filename, lineno, colno, rootInstance)
            console.log('addEventListener error 捕获阶段>>>异常:', e)
        },
        true
    )
    window.addEventListener('unhandledrejection', function(e) {
        // e.preventDefault(); // 阻止异常向上抛出
        let { reason } = e
        log(JSON.stringify(reason), '', '', '', rootInstance)
        console.log('Promise 异常 unhandledrejection :', e)
    })
}

function log(msg, target, rowNum, colNums, vminstance) {
    let msgs = `***[${msg}]***`,
        state = (vminstance && vminstance.$store && vminstance.$store.state) || {}

    // 用户信息
    if (state.userInfo) {
        let { phoneNumber, userId } = state.userInfo
        msgs += `***[phone:${phoneNumber}--userId:${userId}]***`
    }
    // 路由信息
    let { name, fullPath } = (vminstance && vminstance.$route) || {}
    msgs += `***[router-name: ${name} -- router-fullpath: ${fullPath}]***`

    BJ_REPORT.report({
        msg: msgs,
        target,
        rowNum,
        colNums
    })
}

推荐阅读
  • 2022年2月 微信小程序 app.json 配置详解:启用调试模式
    本文将详细介绍如何在微信小程序的 app.json 文件中启用调试模式(debug),并通过实际案例展示其配置方法和应用场景。 ... [详细]
  • 机器学习算法:SVM(支持向量机)
    SVM算法(SupportVectorMachine,支持向量机)的核心思想有2点:1、如果数据线性可分,那么基于最大间隔的方式来确定超平面,以确保全局最优, ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
  • 本文详细介绍了 InfluxDB、collectd 和 Grafana 的安装与配置流程。首先,按照启动顺序依次安装并配置 InfluxDB、collectd 和 Grafana。InfluxDB 作为时序数据库,用于存储时间序列数据;collectd 负责数据的采集与传输;Grafana 则用于数据的可视化展示。文中提供了 collectd 的官方文档链接,便于用户参考和进一步了解其配置选项。通过本指南,读者可以轻松搭建一个高效的数据监控系统。 ... [详细]
  • 在PHP中如何正确调用JavaScript变量及定义PHP变量的方法详解 ... [详细]
  • JUC(三):深入解析AQS
    本文详细介绍了Java并发工具包中的核心类AQS(AbstractQueuedSynchronizer),包括其基本概念、数据结构、源码分析及核心方法的实现。 ... [详细]
  • 本文详细介绍了在 CentOS 7 系统中配置 fstab 文件以实现开机自动挂载 NFS 共享目录的方法,并解决了常见的配置失败问题。 ... [详细]
  • 在Delphi7下要制作系统托盘,只能制作一个比较简单的系统托盘,因为ShellAPI文件定义的TNotifyIconData结构体是比较早的版本。定义如下:1234 ... [详细]
  • 本地存储组件实现对IE低版本浏览器的兼容性支持 ... [详细]
  • PTArchiver工作原理详解与应用分析
    PTArchiver工作原理及其应用分析本文详细解析了PTArchiver的工作机制,探讨了其在数据归档和管理中的应用。PTArchiver通过高效的压缩算法和灵活的存储策略,实现了对大规模数据的高效管理和长期保存。文章还介绍了其在企业级数据备份、历史数据迁移等场景中的实际应用案例,为用户提供了实用的操作建议和技术支持。 ... [详细]
  • DVWA学习笔记系列:深入理解CSRF攻击机制
    DVWA学习笔记系列:深入理解CSRF攻击机制 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 本文介绍了如何利用Struts1框架构建一个简易的四则运算计算器。通过采用DispatchAction来处理不同类型的计算请求,并使用动态Form来优化开发流程,确保代码的简洁性和可维护性。同时,系统提供了用户友好的错误提示,以增强用户体验。 ... [详细]
author-avatar
落花飞雪277590089
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有