Merge branch '2.0'

增加错误日志收集机制
This commit is contained in:
zhigang.li@tendcloud.com 2018-10-11 16:22:56 +08:00
commit d6e47baaad
20 changed files with 338 additions and 8 deletions

5
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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'
})
}

View File

@ -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>

View File

@ -0,0 +1,2 @@
import ErrorStore from './error-store.vue'
export default ErrorStore

View File

@ -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: {

View File

@ -19,5 +19,14 @@ export default {
/**
* @description 默认打开的首页的路由name值默认为home
*/
homeName: 'home'
homeName: 'home',
/**
* @description 需要加载的插件
*/
plugin: {
'error-store': {
showInHeader: true, // 设为false后不会在顶部显示错误日志徽标
developmentOff: false // 设为true后在开发环境不会收集错误信息方便开发中排查错误
}
}
}

View File

@ -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)
})
}

View File

@ -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'
}

View File

@ -27,5 +27,7 @@ export default {
modalTitle: '模态框题目',
content: '这是模态框内容',
buttonText: '显示模态框',
'i18n-tip': '注:仅此页做了多语言,其他页面没有在多语言包中添加语言内容'
'i18n-tip': '注:仅此页做了多语言,其他页面没有在多语言包中添加语言内容',
error_store_page: '错误收集',
error_logger_page: '错误日志'
}

View File

@ -27,5 +27,7 @@ export default {
modalTitle: '模態框題目',
content: '這是模態框內容',
buttonText: '顯示模態框',
'i18n-tip': '注:僅此頁做了多語言,其他頁面沒有在多語言包中添加語言內容'
'i18n-tip': '注:僅此頁做了多語言,其他頁面沒有在多語言包中添加語言內容',
error_store_page: '錯誤收集',
error_logger_page: '錯誤日誌'
}

View File

@ -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 全局注册应用配置

View File

@ -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

View File

@ -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)
})
}
}
}

9
src/plugin/index.js Normal file
View File

@ -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)
}
}

View File

@ -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',

View File

@ -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)
})
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -34,7 +34,7 @@ module.exports = {
.set('_c', resolve('src/components'))
},
// 打包时不生成.map文件
productionSourceMap: false
productionSourceMap: true
// 这里写你调用接口的基础路径来解决跨域如果设置了代理那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串
// devServer: {
// proxy: 'localhost:3000'