Nuxt.js添加错误日志

介绍

Nuxt.js中页面的一些报错,尤其是服务器端请求相关报错信息。在生产环境中看不到,很难定位问题

这里将错误信息前端收集到,发送给服务器端;服务器端记录错误信息到磁盘文件中

这里以集成的服务器端框架为koa为例,其他服务器框架类似

思路

  • 使用errorHandler收集错误信息。参考:全局配置>errorHandler
  • vuex中使用action请求后端接口,将错误信息发送给服务器端
  • 服务器端接收到错误信息后,写入日志文件(以增量的方式写入)
  • 日志以月为单位,不同月的日志存储到不同的文件中
  • 服务器端发送请求时,需要写完整地址;并且手动带上请求头信息

步骤

  • plugins/目录下新建errorlog.js
  • import Vue from 'vue'
    
    export default ({ app, store, route, req, redirect }) => {
      const errorHandler = (error, vm, info) => {
        console.error(error)
        store.dispatch('getErr', {
          err: error.stack,
          hook: info,
          url: vm.$route.fullPath,
          req
        })
      }
    
      Vue.config.errorHandler = errorHandler
      // Vue 实例
      Vue.prototype.$throw = (error, vm, info) => errorHandler(error, vm, info)
      // context - 目前只有 asyncData
      app.$serverThrow = ({ error, route }) => {
        // 服务器端发送的请求,需要手动带上请求头等信息。这里给传过去
        store.dispatch('getErr', {
          err: error.stack,
          hook: 'asyncData',
          url: route.fullPath,
          req
        })
      }
    }

  • 修改nuxt.config.js文件,添加errorlog
  • / ...
    module.exports = {
      // ...
      plugins: [
        // ...
        { src: '@/plugins/errorlog', ssr: true },
        // ...
      ],
      // ...
    }

  • 修改store/index.js文件,添加getErr

 

// ...
import axios from 'axios'
// ...
export const actions = {
  // ...
  getErr({ commit }, errInfo) {
    // 客户端发送请求可以直接使用 /api/getErr
    // 服务器端发送请求需要写上域名和端口 http://127.0.0.1:3000/api/getErr
    // 服务器端发送的请求,在请求头里面没有 user-agent、cookie等信息,需要手动带上
    // 端口号等,也可以在 nuxt.config.js 文件的 env 中配置:port: 3000
    const { err, hook, url, req } = errInfo
    const headers = (req && req.headers) || {}
    if (process.server) {
      axios.post(`http://127.0.0.1:${process.env.port}/api/getErr`, { err, hook, url }, { headers })
    } else {
      axios.post('/api/getErr', { err, hook, url })
    }
  },
  // ...
}
// ...
  • 安装koa-routerkoa-bodynpm i koa-router koa-body --save
  • 修改server/index.js文件,添加router
 // Build in development
  if (config.dev) {
    const builder = new Builder(nuxt)
    await builder.build()
  } else {
    await nuxt.ready()
  }
  // 添加下面一句代码
  app.use(apiRoute.routes()).use(apiRoute.allowedMethods())

  app.use((ctx) => {
    ctx.status = 200
    ctx.respond = false // Bypass Koa's built-in response handling
    ctx.req.ctx = ctx // This might be useful later on, e.g. in nuxtServerInit or with nuxt-stash
    nuxt.render(ctx.req, ctx.res)
  })

  app.listen(port, host)
  consola.ready({
    message: `Server listening on http://${host}:${port}`,
    badge: true
  })
}

start()

 

  • server/目录下新建logs/目录,用来存放日志
  • server/目录下新建api/目录
  • server/api/目录下新建index.js文件
const Router = require('koa-router')
const koaBody = require('koa-body')()
const errorController = require('./error')

const router = new Router({
  prefix: '/api'
})

// 错误日志文件名
const filePath = 'server/logs/'
const filePrefix = 'errorLog'

router.post('/getErr', koaBody, async ctx => {
  const data = ctx.request.body
  const header = ctx.request.header
  const obj = {
    err: data.err,
    hook: data.hook,
    url: data.url,
    userAgent: header['user-agent'],
    cookie: header['cookie']
  }

  try {
    await errorController.fileWrite(filePath, filePrefix, obj)
    ctx.body = { code: 0, msg: 'error log write success!', data: [] }
  } catch (err) {
    ctx.body = { code: 1, msg: err, data: [] }
  }
})

module.exports = router

 

  • server/api/目录下新建error.js文件
const fs = require('fs')

// 获取文件名称 server/logs/errorLog_201901.log
function getFileName(filePath, filePrefix) {
  // filePath最后如果不是 / , 添加 /
  filePath.charAt(filePath.length - 1) !== '/' && (filePath = filePath + '/')

  let date = new Date()
  let year = date.getFullYear()
  let month = date.getMonth() + 1
  month < 10 && (month = '0' + month)

  return `${filePath}${filePrefix}_${year}${month}.log`
}

// 若文件不存在,创建一个
function fileCreate(filename) {
  return new Promise((resolve, reject) => {
    fs.exists(filename, function(exists) {
      if(!exists) {
        fs.writeFile(filename, '', function(err) {
          if(err) {
            // console.log(err)
            reject(new Error('file create failed'))
          }
          resolve()
        })
      } else {
        resolve()
      }
    })
  })
}

// 增量更新日志文件
function fileWrite(filePath, filePrefix, errData) {
  const filename = getFileName(filePath, filePrefix)
  return new Promise((resolve, reject) => {
    // 先读取文件内容
    fileCreate(filename).then(() => {
      fs.readFile(filename, 'utf8', (err, data) => {
        if (err) {
          reject(err)
        }
        data += '\r\n'
        data += '报错内容:' + errData.err + '\r\n'
        data += '所在钩子:' + errData.hook + '\r\n'
        data += '报错时间:' + new Date().toLocaleString() + '\r\n'
        data += '报错页面:' + errData.url + '\r\n'
        data += 'userAgent:' + errData.userAgent + '\r\n'
        data += 'cookie:' + errData.cookie + '\r\n'
        content = data
        // 追加错误内容
        fs.writeFile(filename, content, (err) => {
          if (err) {
            reject(err)
          }
          resolve()
        })
      })
    }).catch((err) => {
      reject(err)
    })
  })
}

module.exports = {
  fileWrite
}

 

  • 重新运行代码。若有错误,会在server/logs/目录下生成一个errorLog_201910.log的文件(其中201910为当前的年和月),此文件存储了记录下来的错误信息
  • 在页面组件的asyncData生命周期中可以手动抛出错误
// ...
import axios from 'axios'
// ...
export default {
  asyncData({ app, store, route }) {
    // ...
    const res = {
      // ...
    }
    return axios.all([
      // ...
    ]).then(axios.spread((/**/) => {
      // ...
      return res
    })).catch(error => {
      app.$serverThrow({ error, route })
      return res
    })
  }
}

 

  • Vue生命周期等地方,有异常会自动抛出错误。也可以在想要的地方手动抛出一个错误
this.$throw('这是手动抛出的一个错误', this, '这是hook,比如:updated')

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值