增加错误日志收集机制
This commit is contained in:
parent
9ddc50078d
commit
b7dd9dfd52
|
|
@ -4298,6 +4298,11 @@
|
|||
"integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
|
||||
"dev": true
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.7.7.tgz",
|
||||
"integrity": "sha512-Qlkiu0NNDpYwhk0syK4ImvAl/5YnsEMkvC2O123INviGeOA3Q8s5VyVkZzmN5SC7Wv9bb1+rfwO+uSqtHB4UWw=="
|
||||
},
|
||||
"de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"codemirror": "^5.38.0",
|
||||
"countup": "^1.8.2",
|
||||
"cropperjs": "^1.2.2",
|
||||
"dayjs": "^1.7.7",
|
||||
"echarts": "^4.0.4",
|
||||
"html2canvas": "^1.0.0-alpha.12",
|
||||
"iview": "^3.1.3",
|
||||
|
|
|
|||
|
|
@ -13,3 +13,18 @@ export const getDragList = () => {
|
|||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export const errorReq = () => {
|
||||
return axios.request({
|
||||
url: 'error_url',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
export const saveErrorLogger = info => {
|
||||
return axios.request({
|
||||
url: 'save_error_logger',
|
||||
data: info,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<div class="error-store">
|
||||
<Badge dot :count="countComputed">
|
||||
<Button type="text" @click="openErrorLoggerPage">
|
||||
<Icon :size="20" type="ios-bug"/>
|
||||
</Button>
|
||||
</Badge>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ErrorStore',
|
||||
props: {
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
hasRead: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
countComputed () {
|
||||
return this.hasRead ? 0 : this.count
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openErrorLoggerPage () {
|
||||
this.$router.push({
|
||||
name: 'error_logger_page'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.error-store{
|
||||
margin-right: 12px;
|
||||
.ivu-badge-dot{
|
||||
top: 20px;
|
||||
}
|
||||
.ivu-btn.ivu-btn-text{
|
||||
padding: 5px 1px 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
import ErrorStore from './error-store.vue'
|
||||
export default ErrorStore
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
<header-bar :collapsed="collapsed" @on-coll-change="handleCollapsedChange">
|
||||
<user :user-avator="userAvator"/>
|
||||
<language v-if="$config.useI18n" @on-lang-change="setLocal" style="margin-right: 10px;" :lang="local"/>
|
||||
<error-store v-if="$config.plugin['error-store'] && $config.plugin['error-store'].showInHeader" :has-read="hasReadErrorPage" :count="errorCount"></error-store>
|
||||
<fullscreen v-model="isFullscreen" style="margin-right: 10px;"/>
|
||||
</header-bar>
|
||||
</Header>
|
||||
|
|
@ -39,7 +40,8 @@ import TagsNav from './components/tags-nav'
|
|||
import User from './components/user'
|
||||
import Fullscreen from './components/fullscreen'
|
||||
import Language from './components/language'
|
||||
import { mapMutations, mapActions } from 'vuex'
|
||||
import ErrorStore from './components/error-store'
|
||||
import { mapMutations, mapActions, mapGetters } from 'vuex'
|
||||
import { getNewTagList, getNextRoute, routeEqual } from '@/libs/util'
|
||||
import minLogo from '@/assets/images/logo-min.jpg'
|
||||
import maxLogo from '@/assets/images/logo.jpg'
|
||||
|
|
@ -52,6 +54,7 @@ export default {
|
|||
Language,
|
||||
TagsNav,
|
||||
Fullscreen,
|
||||
ErrorStore,
|
||||
User
|
||||
},
|
||||
data () {
|
||||
|
|
@ -63,6 +66,9 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'errorCount'
|
||||
]),
|
||||
tagNavList () {
|
||||
return this.$store.state.app.tagNavList
|
||||
},
|
||||
|
|
@ -80,6 +86,9 @@ export default {
|
|||
},
|
||||
local () {
|
||||
return this.$store.state.app.local
|
||||
},
|
||||
hasReadErrorPage () {
|
||||
return this.$store.state.app.hasReadErrorPage
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -19,5 +19,14 @@ export default {
|
|||
/**
|
||||
* @description 默认打开的首页的路由name值,默认为home
|
||||
*/
|
||||
homeName: 'home'
|
||||
homeName: 'home',
|
||||
/**
|
||||
* @description 需要加载的插件
|
||||
*/
|
||||
plugin: {
|
||||
'error-store': {
|
||||
showInHeader: true, // 设为false后不会在顶部显示错误日志徽标
|
||||
developmentOff: false // 设为true后在开发环境不会收集错误信息,方便开发中排查错误
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,17 @@
|
|||
import axios from 'axios'
|
||||
import store from '@/store'
|
||||
// import { Spin } from 'iview'
|
||||
const addErrorLog = errorInfo => {
|
||||
const { statusText, status, request: { responseURL } } = errorInfo
|
||||
let info = {
|
||||
type: 'ajax',
|
||||
code: status,
|
||||
mes: statusText,
|
||||
url: responseURL
|
||||
}
|
||||
if (!responseURL.includes('save_error_logger')) store.dispatch('addErrorLog', info)
|
||||
}
|
||||
|
||||
class HttpRequest {
|
||||
constructor (baseUrl = baseURL) {
|
||||
this.baseUrl = baseUrl
|
||||
|
|
@ -39,6 +51,7 @@ class HttpRequest {
|
|||
return { data, status }
|
||||
}, error => {
|
||||
this.destroy(url)
|
||||
addErrorLog(error.response)
|
||||
return Promise.reject(error)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,5 +27,7 @@ export default {
|
|||
modalTitle: 'Modal Title',
|
||||
content: 'This is the modal box content.',
|
||||
buttonText: 'Show Modal',
|
||||
'i18n-tip': 'Note: Only this page is multi-language, other pages do not add language content to the multi-language package.'
|
||||
'i18n-tip': 'Note: Only this page is multi-language, other pages do not add language content to the multi-language package.',
|
||||
error_store_page: 'Error Collection',
|
||||
error_logger_page: 'Error Logger'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,5 +27,7 @@ export default {
|
|||
modalTitle: '模态框题目',
|
||||
content: '这是模态框内容',
|
||||
buttonText: '显示模态框',
|
||||
'i18n-tip': '注:仅此页做了多语言,其他页面没有在多语言包中添加语言内容'
|
||||
'i18n-tip': '注:仅此页做了多语言,其他页面没有在多语言包中添加语言内容',
|
||||
error_store_page: '错误收集',
|
||||
error_logger_page: '错误日志'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,5 +27,7 @@ export default {
|
|||
modalTitle: '模態框題目',
|
||||
content: '這是模態框內容',
|
||||
buttonText: '顯示模態框',
|
||||
'i18n-tip': '注:僅此頁做了多語言,其他頁面沒有在多語言包中添加語言內容'
|
||||
'i18n-tip': '注:僅此頁做了多語言,其他頁面沒有在多語言包中添加語言內容',
|
||||
error_store_page: '錯誤收集',
|
||||
error_logger_page: '錯誤日誌'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import iView from 'iview'
|
|||
import i18n from '@/locale'
|
||||
import config from '@/config'
|
||||
import importDirective from '@/directive'
|
||||
import installPlugin from '@/plugin'
|
||||
import 'iview/dist/styles/iview.css'
|
||||
import './index.less'
|
||||
import '@/assets/icons/iconfont.css'
|
||||
|
|
@ -18,6 +19,13 @@ if (process.env.NODE_ENV !== 'production') require('@/mock')
|
|||
Vue.use(iView, {
|
||||
i18n: (key, value) => i18n.t(key, value)
|
||||
})
|
||||
/**
|
||||
* @description 注册admin内置插件
|
||||
*/
|
||||
installPlugin(Vue)
|
||||
/**
|
||||
* @description 生产环境关掉提示
|
||||
*/
|
||||
Vue.config.productionTip = false
|
||||
/**
|
||||
* @description 全局注册应用配置
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ Mock.mock(/\/get_info/, getUserInfo)
|
|||
Mock.mock(/\/logout/, logout)
|
||||
Mock.mock(/\/get_table_data/, getTableData)
|
||||
Mock.mock(/\/get_drag_list/, getDragList)
|
||||
Mock.mock(/\/save_error_logger/, 'success')
|
||||
|
||||
export default Mock
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import store from '@/store'
|
||||
export default {
|
||||
install (Vue, options) {
|
||||
if (options.developmentOff && process.env.NODE_ENV === 'development') return
|
||||
Vue.config.errorHandler = (error, vm, mes) => {
|
||||
let info = {
|
||||
type: 'script',
|
||||
code: 0,
|
||||
mes: error.message,
|
||||
url: window.location.href
|
||||
}
|
||||
Vue.nextTick(() => {
|
||||
store.dispatch('addErrorLog', info)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import config from '@/config'
|
||||
const { plugin } = config
|
||||
|
||||
export default (Vue) => {
|
||||
for (let name in plugin) {
|
||||
const value = plugin[name]
|
||||
Vue.use(require(`./${name}`).default, typeof value === 'object' ? value : undefined)
|
||||
}
|
||||
}
|
||||
|
|
@ -241,6 +241,45 @@ export default [
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/error_store',
|
||||
name: 'error_store',
|
||||
meta: {
|
||||
hide: true
|
||||
},
|
||||
component: Main,
|
||||
children: [
|
||||
{
|
||||
path: 'error_store_page',
|
||||
name: 'error_store_page',
|
||||
meta: {
|
||||
icon: 'ios-bug',
|
||||
title: '错误收集'
|
||||
},
|
||||
component: () => import('@/view/error-store/error-store.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/error_logger',
|
||||
name: 'error_logger',
|
||||
meta: {
|
||||
hide: true,
|
||||
hideInMenu: true
|
||||
},
|
||||
component: Main,
|
||||
children: [
|
||||
{
|
||||
path: 'error_logger_page',
|
||||
name: 'error_logger_page',
|
||||
meta: {
|
||||
icon: 'ios-bug',
|
||||
title: '错误收集'
|
||||
},
|
||||
component: () => import('@/view/single-page/error-logger.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/directive',
|
||||
name: 'directive',
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
localRead
|
||||
} from '@/libs/util'
|
||||
import beforeClose from '@/router/before-close'
|
||||
import { saveErrorLogger } from '@/api/data'
|
||||
import router from '@/router'
|
||||
import routers from '@/router/routers'
|
||||
import config from '@/config'
|
||||
|
|
@ -30,10 +31,13 @@ export default {
|
|||
breadCrumbList: [],
|
||||
tagNavList: [],
|
||||
homeRoute: getHomeRoute(routers, homeName),
|
||||
local: localRead('local')
|
||||
local: localRead('local'),
|
||||
errorList: [],
|
||||
hasReadErrorPage: false
|
||||
},
|
||||
getters: {
|
||||
menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access)
|
||||
menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access),
|
||||
errorCount: state => state.errorList.length
|
||||
},
|
||||
mutations: {
|
||||
setBreadCrumb (state, route) {
|
||||
|
|
@ -81,6 +85,24 @@ export default {
|
|||
setLocal (state, lang) {
|
||||
localSave('local', lang)
|
||||
state.local = lang
|
||||
},
|
||||
addError (state, error) {
|
||||
state.errorList.push(error)
|
||||
},
|
||||
setHasReadErrorLoggerStatus (state, status = true) {
|
||||
state.hasReadErrorPage = status
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
addErrorLog ({ commit }, info) {
|
||||
if (!window.location.href.includes('error_logger_page')) commit('setHasReadErrorLoggerStatus', false)
|
||||
let data = {
|
||||
...info,
|
||||
time: Date.parse(new Date())
|
||||
}
|
||||
saveErrorLogger(info).then(() => {
|
||||
commit('addError', data)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<template>
|
||||
<div>
|
||||
<Card>
|
||||
iview-admin会自动将你程序中的错误收集起来,你可以将错误日志发给后端保存起来。如果你不需要这个功能,将'./src/config/index.js'里的plugin的'error-store'属性删掉即可。
|
||||
另外在开发环境下,你程序中的错误都会被收集起来,这样可能不利于你排查错误,你可以将'./src/config/index.js'的'error-store'的'developmentOff'设为true。
|
||||
如果你只是想收集错误日志,不希望登录用户看到错误日志,你可以不提供查看日志的入口,只需将'./src/config/index.js'的'error-store'的'showInHeader'设为false。
|
||||
</Card>
|
||||
<Card style="margin-top: 20px;">
|
||||
<Row>
|
||||
<i-col span="4">
|
||||
<Button @click="click">点击测试触发程序错误</Button>
|
||||
</i-col>
|
||||
<i-col span="4">
|
||||
<Button @click="ajaxClick">点击测试触发ajax接口请求错误</Button>
|
||||
</i-col>
|
||||
<i-col span="16">
|
||||
ajax接口请求是请求easy-mock的一个不存在接口,所以服务端会报404错误,错误收集机制会收集这个错误,测试的时候有一定网络延迟,所以点击按钮之后稍等一会才会收集到错误。
|
||||
</i-col>
|
||||
</Row>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { errorReq } from '@/api/data'
|
||||
export default {
|
||||
name: 'error_store_page',
|
||||
methods: {
|
||||
click () {
|
||||
console.log(admin)
|
||||
},
|
||||
ajaxClick () {
|
||||
errorReq()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<div>
|
||||
<Table :columns="columns" :data="errorList"></Table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import { mapMutations } from 'vuex'
|
||||
export default {
|
||||
name: 'error_logger_page',
|
||||
data () {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
type: 'index',
|
||||
title: '序号',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
title: '类型',
|
||||
width: 100,
|
||||
render: (h, { row }) => {
|
||||
return (
|
||||
<div>
|
||||
<icon size={16} type={row.type === 'ajax' ? 'md-link' : 'md-code-working'}></icon>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'code',
|
||||
title: '编码',
|
||||
render: (h, { row }) => {
|
||||
return (
|
||||
<span>{ row.code === 0 ? '-' : row.code }</span>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'mes',
|
||||
title: '信息'
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
title: 'URL'
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
title: '时间',
|
||||
render: (h, { row }) => {
|
||||
return (
|
||||
<span>{ dayjs(row.time).format('YYYY-MM-DD HH:mm:ss') }</span>
|
||||
)
|
||||
},
|
||||
sortable: true,
|
||||
sortType: 'desc'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
errorList () {
|
||||
return this.$store.state.app.errorList
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([
|
||||
'setHasReadErrorLoggerStatus'
|
||||
])
|
||||
},
|
||||
activated () {
|
||||
this.setHasReadErrorLoggerStatus()
|
||||
},
|
||||
mounted () {
|
||||
this.setHasReadErrorLoggerStatus()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -34,7 +34,7 @@ module.exports = {
|
|||
.set('_c', resolve('src/components'))
|
||||
},
|
||||
// 打包时不生成.map文件
|
||||
productionSourceMap: false
|
||||
productionSourceMap: true
|
||||
// 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串
|
||||
// devServer: {
|
||||
// proxy: 'localhost:3000'
|
||||
|
|
|
|||
Loading…
Reference in New Issue