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
const Koa = require('koa') const consola = require('consola') const { Nuxt, Builder } = require('nuxt') // 添加下面一句代码 const apiRoute = require('./api') const app = new Koa() // Import and Set Nuxt.js options const config = require('../nuxt.config.js') config.dev = app.env !== 'production' async function start () { // Instantiate nuxt.js const nuxt = new Nuxt(config) const { host = process.env.HOST || '127.0.0.1', port = process.env.PORT || 3000 } = nuxt.options.server // 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')

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: