“This is the 8th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021”

preface

When something goes wrong online, the environment often reproduces the trouble, and many of the errors are unknown to the developers if no one reports back. After a user reports an error, it is difficult to locate the error in a short time.

At present, there are a lot of front-end monitoring on the market, and many easy to use, and some frameworks can do operation playback. But sometimes I want to add my own things in the wrong days, I don’t need that complicated function, I just want to prompt me in time. For example, I can send error messages to my peggroup in a timely manner, and do not need to have the backend database.

Implementation approach

I took a look at other people’s ideas for implementing front-end monitoring and came up with these:

First, js comes with window.onError which can be used to collect errors

function handleOnError(errorMessage, scriptURI, LineNumber, columnNumber errorObj) {/ / some code return true / / stop the browser console output} function handleListenerError (eventErr){// Some code eventerr.preventdefault () // Prevent browser console output} windot.onerror = handleOnError window.addEventListener('error', handleListenerError, true);Copy the code

Vue provides vue.config. errorHandler for error collection

ErrorHandler = function (err, vm, info) {// Handle error // 'info' is a Vue specific error message. Console. log (err, VM, info)} for example, the error lifecycle hook // only available in 2.2.0+ console.log (err, VM, info)}Copy the code

For vUE files that are compressed, see the vUE error log handling section below for details on how to locate the appropriate code.

Another kind is the request error, can pass the XMLHttpRequest. Prototype. Add in the send method to monitor.

With a way to catch the error, all that is left is to tell the background about the error.

Here are three ways:

1. Make requests directly through the request framework in the project (e.g. across the nginx direction proxy)

2. Request a 1px * 1px GIF file from the new image SRC (this can solve the reverse proxy)

3. Use navigator.sendBeacon, which has the benefit of delaying asynchronous execution without hindering others.

Note:

Another is to judge whether it is the development environment, test environment, don’t in the development, testing, the nail ding on the embarrassment.

If you don’t need help, you can also start a Node service and send an email to your mailbox. You can do it.

Implementation method

implementation

My project is using VUE, below directly on the code

index.js

import Report from './report.js' import Vue from 'vue' const isProduction = process.env.NODE_ENV === 'development' let MyRport = new Report({url: 'error log upload link ', module:' project '}) if (! Prototype var send = xhr.send xhr.send = function () {let Loadend = arg => {const currentTarget = arg. CurrentTarget // Request return code non-200 if (currentTarget. Status! == 200) { myRport.xhrHandler(currentTarget.responseURL, currentTarget.status, currentTarget.response) } this.removeEventListener('loadend', loadend, false) } this.addEventListener('loadend', Loadend, false) return send.apply(this, arguments)} /** Handle Vue error */ vue.config. errorHandler = function (err, vm, arguments) Info) {// Handle error // 'info' is Vue specific error information, such as the lifecycle hook where the error occurred // Only available in 2.2.0+ myrport.handler (err, vm, info)}}Copy the code

report.js

// taken and reworked from Vue.js source
import logRequest from './request.js'
const ErrorStackParser = require('error-stack-parser')
const StackGenerator = require('stack-generator')
const reduce = (arr, fn, accum) => {
  let val = accum
  for (let i = 0, len = arr.length; i < len; i++) val = fn(val, arr[i], i, arr)
  return val
}

// Array#filter
const filter = (arr, fn) =>
  reduce(arr, (accum, item, i, arr) => !fn(item, i, arr) ? accum : accum.concat(item), [])

// Array#map
// const map = (arr, fn) =>
//   reduce(arr, (accum, item, i, arr) => accum.concat(fn(item, i, arr)), [])

// // Array#includes
// const includes = (arr, x) =>
//   reduce(arr, (accum, item, i, arr) => accum === true || item === x, false)

const classify = str => str.replace(/(?:^|[-_])(\w)/g, c => c.toUpperCase()).replace(/[-_]/g, '')

let dateFormat = function (fmt) { // author: meizz
  var o = {
    'M+': this.getMonth() + 1, // 月份
    'd+': this.getDate(), // 日
    'h+': this.getHours(), // 小时
    'm+': this.getMinutes(), // 分
    's+': this.getSeconds(), // 秒
    'q+': Math.floor((this.getMonth() + 3) / 3), // 季度
    'S': this.getMilliseconds() // 毫秒
  }
  if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)) }
  for (var k in o) {
    if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) }
  }
  return fmt
}

// taken and reworked from Vue.js source
const formatComponentName = (vm, includeFile) => {
  if (vm.$root === vm) return '<Root>'
  const options = typeof vm === 'function' && vm.cid != null
    ? vm.options
    : vm._isVue
      ? vm.$options || vm.constructor.options
      : vm || {}
  let name = options.name || options._componentTag
  const file = options.__file
  if (!name && file) {
    const match = file.match(/([^/\]+).vue$/)
    name = match && match[1]
  }

  return (
    (name ? ('<' + (classify(name)) + '>') : '<Anonymous>') +
    (file && includeFile !== false ? (' at ' + file) : '')
  )
}

const hasStack = (err) => {
  return !!err &&
    (!!err.stack || !!err.stacktrace || !!err['opera#sourceloc']) &&
    typeof (err.stack || err.stacktrace || err['opera#sourceloc']) === 'string' &&
    err.stack !== `${err.name}: ${err.message}`
}

const getStacktrace = function (error, errorFramesToSkip = 0, generatedFramesToSkip = 0) {
  if (hasStack(error)) {
    return ErrorStackParser.parse(error).slice(errorFramesToSkip)
  }
  // in IE11 a new Error() doesn't have a stacktrace until you throw it, so try that here
  try {
    throw error
  } catch (e) {
    if (hasStack(e)) return ErrorStackParser.parse(error).slice(1 + generatedFramesToSkip)
    // error wasn't provided or didn't have a stacktrace so try to walk the callstack
    return filter(StackGenerator.backtrace(), frame =>
      (frame.functionName || '').indexOf('StackGenerator$$') === -1
    ).slice(1 + generatedFramesToSkip)
  }
}

const normaliseFunctionName = name => /^global code$/i.test(name) ? 'global code' : name
const formatStackframe = frame => {
  const f = {
    file: frame.fileName,
    method: normaliseFunctionName(frame.functionName),
    lineNumber: frame.lineNumber,
    columnNumber: frame.columnNumber,
    code: undefined,
    inProject: undefined
  }
  // Some instances result in no file:
  // - calling notify() from chrome's terminal results in no file/method.
  // - non-error exception thrown from global code in FF
  // This adds one.
  if (f.lineNumber > -1 && !f.file && !f.method) {
    f.file = 'global code'
  }
  return f
}

class Report {
  constructor (options) {
    this.options = options
  }

  doSendRequest (data) {
    if (data.requestAddress && data.requestAddress.indexOf('错误日志上传链接') > -1) {
      // 上报链接需要排徐,否则可能导致无限循环
      // return false
    } else {
      if (this.options.sendRrquest && typeof (this.options.sendRrquest) === 'function') {
        this.options.sendRrquest(data)
      } else {
        var params = new URLSearchParams()
        params.append('parameter', JSON.stringify({ body: { request: data } }))
        logRequest.sendRrquest({
          method: 'post',
          url: this.options.url,
          data: params,
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
          }
        })
      }
    }
  }

  /**
   * vue 错误日志处理
   */
  handler (err, vm, info) {
    let stacktraceArr = getStacktrace(err)
    let stacktrace = reduce(stacktraceArr, (accum, frame) => {
      const f = formatStackframe(frame)
      // don't include a stackframe if none of its properties are defined
      try {
        if (JSON.stringify(f) === '{}') return accum
        return accum.concat(f)
      } catch (e) {
        return accum
      }
    }, [])

    let errorOption = {
      name: err.name,
      message: err.message,
      stacktrace: stacktrace,
      errorInfo: info,
      props: vm ? vm.$options.propsData : undefined,
      data: vm ? vm.$data : undefined
    }
    // logLevel级别 DEBUG、INFO、WARN、ERROR
    let logData = {
      module: this.options.module,
      logLevel: 'ERROR',
      logType: 'js报错',
      time: dateFormat.call(new Date(), 'yyyy-MM-dd hh:mm:ss'),
      clientInfo: window.navigator.userAgent.toLowerCase(), // 客户端信息
      errInfoAndCallStack: JSON.stringify(errorOption), // 错误的信息和调用堆栈
      url: window.location.href,
      // requestAddress: '',
      // requestTake: 0,
      errLocation: vm ? formatComponentName(vm, true) : '1'
    }
    this.doSendRequest(logData)
  }

  /**
  * http 错误日志处理
  */
  xhrHandler (url, xhrStatus, xhrErrRespons) {
    // logLevel级别 DEBUG、INFO、WARN、ERROR
    let xhrData = {
      module: this.options.module,
      logLevel: 'ERROR',
      logType: 'xhr报错',
      time: dateFormat.call(new Date(), 'yyyy-MM-dd hh:mm:ss'),
      clientInfo: window.navigator.userAgent.toLowerCase(),
      errInfoAndCallStack: xhrErrRespons,
      url: window.location.href,
      requestAddress: url,
      requestTake: xhrStatus,
      errLocation: '1'
    }
    this.doSendRequest(xhrData)
  }

  /**
   * 页面性能监控
   */
  reportPerformance () {
    // 浏览器不支持,就算了!
    if (!window.performance && !window.performance.getEntries) {
      return false
    }

    var result = []
    // 获取当前页面所有请求对应的PerformanceResourceTiming对象进行分析
    window.performance.getEntries().forEach(item => {
      result.push({
        url: item.name,
        entryType: item.entryType,
        type: item.initiatorType,
        'duration(ms)': item.duration
      })
    })
    return result
  }
}

export default Report
Copy the code

Send the wrong code will not send. It also says how to pronounce it in front.