介绍
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-router
和koa-body
:npm 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')
发表评论