├── static
└── .gitkeep
├── config
├── prod.env.js
├── dev.env.js
└── index.js
├── src
├── assets
│ └── logo.png
├── plugins
│ ├── install.js
│ └── index.js
├── main.js
├── App.vue
└── components
│ └── HelloWorld.vue
├── .editorconfig
├── .gitignore
├── .babelrc
├── .postcssrc.js
├── index.html
├── README.md
├── rollup.config.js
└── package.json
/static/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qinmudi/vue-error-report/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | /dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Editor directories and files
9 | .idea
10 | .vscode
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | vue-error-report
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/plugins/install.js:
--------------------------------------------------------------------------------
1 | import wiierror from './index'
2 |
3 | export default function install(Vue, options) {
4 | if (this.install.installed) return false
5 |
6 | let {isReport,reportUrl,appId} = options
7 |
8 | if (!reportUrl&&!appId) {
9 | return console.error(`reportUrl&appId is required`)
10 | }
11 |
12 | wiierror.reportUrl = reportUrl
13 | wiierror.options = Object.assign({},wiierror.options,{app_id: appId})
14 |
15 | Object.defineProperty(Vue.prototype, '$wiierror', {
16 | get: () => this
17 | })
18 |
19 | if(isReport){
20 | wiierror.init()
21 | }
22 |
23 | this.install.installed = true
24 | }
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import VueErrorReport from './plugins/index'
6 |
7 | Vue.config.productionTip = false
8 |
9 | Vue.use(VueErrorReport,{
10 | isReport: true,
11 | reportUrl: 'http://dev.sqm.wiiqq.com/api/fe/save',
12 | appId: '86805a7139a2b8000'
13 | });
14 |
15 | /* eslint-disable no-new */
16 | new Vue({
17 | el: '#app',
18 | components: { App },
19 | template: '',
20 | mounted(){
21 | var a = [];
22 | console.log(a.b.f)
23 | console.log('init')
24 | }
25 | })
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-error-report
2 |
3 | > 平台组前端错误上报插件,会自动收集以下几点:
4 | * 问题静态资源加载
5 | * Ajax请求
6 | * Vue语法错误
7 | * Javascript语法错误
8 |
9 | ## 安装
10 |
11 | ```
12 | npm i vue-error-report --save
13 | ```
14 |
15 | ## 引入
16 |
17 | ```javascript
18 | import VueErrorReport from 'vue-error-report'
19 |
20 | Vue.use(VueErrorReport,options)
21 | ```
22 |
23 | ## 实例
24 | ```javascript
25 | Vue.use(VueErrorReport,{
26 | isReport: true,
27 | reportUrl: 'https://ping.qq.com',
28 | appId: ''
29 | })
30 | ```
31 |
32 | ### options
33 |
34 | | 参数名 | 参数说明 | 是否必填 |
35 | | - | :-: | -:|
36 | | isReport | 是否开启上报 | 必填 |
37 | | reportUrl | 上报地址 | 必填 |
38 | | appId | 项目id | 必填 |
39 |
40 | ## 后置初始化
41 | ```javascript
42 | //当isReport等于 false 时,可以手工调用 this.$wiierror.init() 进行初始化
43 | this.$wiierror.init()
44 | ```
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | require('shelljs/global');
2 | import vue from 'rollup-plugin-vue2';
3 | import buble from 'rollup-plugin-buble';
4 | import nodeResolve from 'rollup-plugin-node-resolve';
5 | import uglify from 'rollup-plugin-uglify';
6 | import replace from 'rollup-plugin-replace';
7 |
8 | rm('-rf', 'dist');
9 | mkdir('-p', 'dist');
10 |
11 | var entry = 'src/plugins/index.js';
12 |
13 | export default {
14 | entry: entry,
15 | dest: 'dist/errorreport.js',
16 | format: 'cjs',
17 | sourceMap: true,
18 | plugins: [
19 | vue(),
20 | buble({
21 | objectAssign: 'Object.assign',
22 | exclude: 'node_modules/**' // only transpile our source code
23 | }),
24 | nodeResolve({
25 | browser: true,
26 | jsnext: true,
27 | main: true,
28 | // pass custom options to the resolve plugin
29 | customResolveOptions: {
30 | moduleDirectory: 'node_modules'
31 | }
32 | }),
33 | uglify(),
34 | replace({
35 | 'process.env.NODE_ENV': JSON.stringify( 'production' )
36 | })
37 | ],
38 | external: ['vue']
39 | }
40 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |

6 |
![未知图片]()
7 |
8 |
9 |
10 |
11 |
12 |
66 |
67 |
77 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-error-report",
3 | "version": "1.0.12",
4 | "description": "vue 错误上报插件",
5 | "author": "秦睦迪 ",
6 | "main": "dist/errorreport.js",
7 | "scripts": {
8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 | "start": "npm run dev",
10 | "build": "rollup -c"
11 | },
12 | "dependencies": {
13 | "vue": "^2.5.2",
14 | "webpack-zepto": "0.0.1"
15 | },
16 | "devDependencies": {
17 | "autoprefixer": "^7.1.2",
18 | "babel-core": "^6.22.1",
19 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
20 | "babel-loader": "^7.1.1",
21 | "babel-plugin-syntax-jsx": "^6.18.0",
22 | "babel-plugin-transform-runtime": "^6.22.0",
23 | "babel-plugin-transform-vue-jsx": "^3.5.0",
24 | "babel-preset-env": "^1.3.2",
25 | "babel-preset-stage-2": "^6.22.0",
26 | "chalk": "^2.0.1",
27 | "copy-webpack-plugin": "^4.0.1",
28 | "css-loader": "^0.28.0",
29 | "extract-text-webpack-plugin": "^3.0.0",
30 | "file-loader": "^1.1.4",
31 | "friendly-errors-webpack-plugin": "^1.6.1",
32 | "html-webpack-plugin": "^2.30.1",
33 | "node-notifier": "^5.1.2",
34 | "optimize-css-assets-webpack-plugin": "^3.2.0",
35 | "ora": "^1.2.0",
36 | "portfinder": "^1.0.13",
37 | "postcss-import": "^11.0.0",
38 | "postcss-loader": "^2.0.8",
39 | "postcss-url": "^7.2.1",
40 | "rimraf": "^2.6.0",
41 | "rollup-plugin-babel": "^3.0.1",
42 | "rollup-plugin-buble": "^0.15.0",
43 | "rollup-plugin-commonjs": "^8.1.0",
44 | "rollup-plugin-node-resolve": "^3.0.0",
45 | "rollup-plugin-replace": "^1.1.1",
46 | "rollup-plugin-uglify": "^2.0.1",
47 | "rollup-plugin-vue2": "^0.8.0",
48 | "selenium-server": "^3.0.1",
49 | "semver": "^5.3.0",
50 | "shelljs": "^0.7.6",
51 | "uglifyjs-webpack-plugin": "^1.1.1",
52 | "url-loader": "^0.5.8",
53 | "vue-loader": "^13.3.0",
54 | "vue-style-loader": "^3.0.1",
55 | "vue-template-compiler": "^2.5.2",
56 | "webpack": "^3.6.0",
57 | "webpack-bundle-analyzer": "^2.9.0",
58 | "webpack-dev-server": "^2.9.1",
59 | "webpack-merge": "^4.1.0"
60 | },
61 | "engines": {
62 | "node": ">= 6.0.0",
63 | "npm": ">= 3.0.0"
64 | },
65 | "browserslist": [
66 | "> 1%",
67 | "last 2 versions",
68 | "not ie <= 8"
69 | ]
70 | }
71 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {
14 | '/wmu': {
15 | target: 'http://wii2app.wiiqq.com',
16 | changeOrigin: true
17 | }
18 | },
19 |
20 | // Various Dev Server settings
21 | host: 'localhost', // can be overwritten by process.env.HOST
22 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
23 | autoOpenBrowser: false,
24 | errorOverlay: true,
25 | notifyOnErrors: true,
26 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
27 |
28 |
29 | /**
30 | * Source Maps
31 | */
32 |
33 | // https://webpack.js.org/configuration/devtool/#development
34 | devtool: 'cheap-module-eval-source-map',
35 |
36 | // If you have problems debugging vue-files in devtools,
37 | // set this to false - it *may* help
38 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
39 | cacheBusting: true,
40 |
41 | cssSourceMap: true
42 | },
43 |
44 | build: {
45 | // Template for index.html
46 | index: path.resolve(__dirname, '../dist/index.html'),
47 |
48 | // Paths
49 | assetsRoot: path.resolve(__dirname, '../dist'),
50 | assetsSubDirectory: 'static',
51 | assetsPublicPath: '/',
52 |
53 | /**
54 | * Source Maps
55 | */
56 |
57 | productionSourceMap: true,
58 | // https://webpack.js.org/configuration/devtool/#production
59 | devtool: '#source-map',
60 |
61 | // Gzip off by default as many popular static hosts such as
62 | // Surge or Netlify already gzip all static assets for you.
63 | // Before setting to `true`, make sure to:
64 | // npm install --save-dev compression-webpack-plugin
65 | productionGzip: false,
66 | productionGzipExtensions: ['js', 'css'],
67 |
68 | // Run the build command with an extra argument to
69 | // View the bundle analyzer report after build finishes:
70 | // `npm run build --report`
71 | // Set to `true` or `false` to always turn it on or off
72 | bundleAnalyzerReport: process.env.npm_config_report
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
Essential Links
5 |
48 |
Ecosystem
49 |
83 |
84 |
85 |
86 |
96 |
97 |
98 |
114 |
--------------------------------------------------------------------------------
/src/plugins/index.js:
--------------------------------------------------------------------------------
1 | import install from './install'
2 |
3 | /*
4 | *格式化参数
5 | */
6 | const formatParams = function(data) {
7 | var arr = [];
8 | for (var name in data) {
9 | arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name]));
10 | }
11 | return arr.join("&");
12 | }
13 |
14 | const wiierror = {
15 | reportUrl: '',
16 | options: {
17 | timespan: '', //发送数据时的时间戳
18 | level: 'error', //js日志错误级别,如warning, error, info, debug
19 | msg: '', //错误的具体信息,
20 | user_agent: navigator.userAgent, //userAgent
21 | app_id: '', //项目 id
22 | url: location.href, //上报页面地址
23 | stack: '', //错误堆栈
24 | data: {} //更多错误信息
25 | },
26 | install,
27 | processStackMsg(error) {
28 | var stack = error.stack
29 | .replace(/\n/gi, '') // 去掉换行,节省传输内容大小
30 | .replace(/\bat\b/gi, '@') // chrome中是at,ff中是@
31 | .split('@') // 以@分割信息
32 | .slice(0, 9) // 最大堆栈长度(Error.stackTraceLimit = 10),所以只取前10条
33 | .map((v) => v.replace(/^\s*|\s*$/g, '')) // 去除多余空格
34 | .join('~') // 手动添加分隔符,便于后期展示
35 | .replace(/\?[^:]+/gi, ''); // 去除js文件链接的多余参数(?x=1之类)
36 | var msg = error.toString();
37 | if (stack.indexOf(msg) < 0) {
38 | stack = msg + '@' + stack;
39 | }
40 | return stack;
41 | },
42 | formatComponentName(vm) {
43 | if (vm.$root === vm) return 'root';
44 | var name = vm._isVue ? (vm.$options && vm.$options.name) || (vm.$options && vm.$options._componentTag) : vm.name;
45 | return (name ? 'component <' + name + '>' : 'anonymous component') + (vm._isVue && vm.$options && vm.$options.__file ? ' at ' + (vm.$options && vm.$options.__file) : '');
46 | },
47 | report_performance() {
48 | /* performance 对象 */
49 | var performance = window.webkitPerformance || window.msPerformance || window.performance;
50 | /* 所需信息 */
51 | var points = [
52 | 'navigationStart', /* 开始浏览的时间 */
53 | 'unloadEventStart', 'unloadEventEnd', /* 卸载上一个页面的时间 */
54 | 'redirectStart', 'redirectEnd', /* HTTP重定向所消耗的时间 */
55 | 'fetchStart', 'domainLookupStart', /* 缓存加载的时间 */
56 | 'domainLookupStart', 'domainLookupEnd', /* DNS查询的时间 */
57 | 'connectStart', 'connectEnd', /* 建立TCP连接的时间 */
58 | 'connectStart', 'requestStart', 'responseStart', 'responseEnd', /* 建立TCP连接的时间 */
59 | 'domInteractive', /* 可以交互的时间 */
60 | 'domContentLoadedEventStart', 'domContentLoadedEventEnd', /* DomContentLoaded 页面加载完成的时间*/
61 | 'domLoading', 'domComplete', /* 页面渲染的时间 */
62 | 'domLoading', 'navigationStart', /* 加载页面花费的总时间 */
63 | 'loadEventStart', 'loadEventEnd', /* 加载事件的时间 */
64 | 'jsHeapSizeLimit', 'totalJSHeapSize', 'usedJSHeapSize', /* 内存的使用情况 */
65 | 'redirectCount', 'type' /* 页面重定向的次数和类型 */
66 | ]
67 | /* 性能对象的属性 */
68 | var timing = performance.timing,
69 | memory = performance.memory,
70 | navigation = performance.navigation
71 | /* 判断性能对象是否可用 */
72 | if (performance && timing && memory && navigation) {
73 | /* 组装统计的信息 */
74 | var m = {
75 | timing: timing,
76 | memory: memory,
77 | navigation: navigation,
78 | userAgent: navigator.userAgent,
79 | url: location.href,
80 | data: + new Date /* + 相当于 .valueOf() */
81 | }
82 | /* 打印出上传的信息 */
83 | console.log(m);
84 | }
85 | },
86 | init(){
87 | //Ajax监控
88 | var s_ajaxListener = new Object();
89 | s_ajaxListener.tempSend = XMLHttpRequest.prototype.send; //复制原先的send方法
90 | s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open; //复制原先的open方法
91 | //重写open方法,记录请求的url
92 | XMLHttpRequest.prototype.open = function(method, url, boolen) {
93 | s_ajaxListener.tempOpen.apply(this, [method, url, boolen]);
94 | this.ajaxUrl = url;
95 |
96 | };
97 | XMLHttpRequest.prototype.send = function(_data) {
98 | var oldReq = this.onreadystatechange
99 | this.onreadystatechange = function() {
100 | if (this.readyState == 4) {
101 | if (this.status >= 200 && this.status < 300) {
102 | oldReq?oldReq.apply(this, [_data]):''
103 | } else {
104 | wiierror.options.msg = 'ajax请求错误';
105 | wiierror.options.stack = `错误码:${this.status}`
106 | wiierror.options.data = JSON.stringify({
107 | fileName: this.ajaxUrl,
108 | category: 'ajax',
109 | text: this.statusText,
110 | status: this.status
111 | })
112 | // 合并上报的数据,包括默认上报的数据和自定义上报的数据
113 | var reportData = Object.assign({}, wiierror.options)
114 | // 把错误信息发送给后台
115 | wiierror.sendReport(reportData)
116 | }
117 | }
118 | }
119 | s_ajaxListener.tempSend.apply(this, [_data])
120 | };
121 |
122 | //监控资源加载错误(img,script,css,以及jsonp)
123 | window.addEventListener('error', function(e) {
124 | var target = e.target ? e.target : e.srcElement;
125 | wiierror.options.msg = e.target.localName + ' is load error';
126 | wiierror.options.stack = 'resouce is not found';
127 | wiierror.options.data = JSON.stringify({
128 | tagName: e.target.localName,
129 | html: target.outerHTML,
130 | type: e.type,
131 | fileName: e.target.currentSrc,
132 | category: 'resource'
133 | });
134 | if (e.target != window) {
135 | //抛去js语法错误
136 | // 合并上报的数据,包括默认上报的数据和自定义上报的数据
137 | var reportData = Object.assign({}, wiierror.options);
138 | wiierror.sendReport(reportData)
139 | }
140 | }, true);
141 |
142 | //监控js错误
143 | window.onerror = function(msg, _url, line, col, error) {
144 | if (msg === 'Script error.' && !_url) {
145 | return false;
146 | }
147 | //采用异步的方式,避免阻塞
148 | setTimeout(function() {
149 | //不一定所有浏览器都支持col参数,如果不支持就用window.event来兼容
150 | col = col || (window.event && window.event.errorCharacter) || 0;
151 | if (error && error.stack) {
152 | //msg信息较少,如果浏览器有追溯栈信息,使用追溯栈信息
153 | wiierror.options.msg = msg;
154 | wiierror.options.stack = error.stack;
155 | } else {
156 | wiierror.options.msg = msg;
157 | wiierror.options.stack = '';
158 | }
159 | wiierror.options.data = JSON.stringify({
160 | url: this.ajaxUrl,
161 | fileName: _url,
162 | category: 'javascript',
163 | line: line,
164 | col: col
165 | })
166 | // 合并上报的数据,包括默认上报的数据和自定义上报的数据
167 | var reportData = Object.assign({}, wiierror.options)
168 | // 把错误信息发送给后台
169 | wiierror.sendReport(reportData)
170 | }, 0);
171 |
172 | return true; //错误不会console浏览器上,如需要,可将这样注释
173 | }
174 |
175 | //监控 promise 异常
176 | window.addEventListener('unhandledrejection', function(event){
177 | // 进行各种处理
178 | wiierror.options.msg = event.reason;
179 | wiierror.options.data = JSON.stringify({
180 | url: location.href,
181 | category: 'promise'
182 | })
183 | wiierror.options.stack = 'promise is error';
184 | var reportData = Object.assign({}, wiierror.options);
185 | wiierror.sendReport(reportData)
186 | // 如果想要阻止继续抛出,即会在控制台显示 `Uncaught(in promise) Error` 的话,调用以下函数
187 | event.preventDefault()
188 | },true)
189 |
190 | //Vue异常监控
191 | Vue.config.errorHandler = (error, vm, info) => {
192 | var componentName = wiierror.formatComponentName(vm);
193 | // var propsData = vm.$options && vm.$options.propsData;
194 |
195 | wiierror.options.msg = error.message;
196 | wiierror.options.stack = wiierror.processStackMsg(error);
197 | wiierror.options.data = JSON.stringify({
198 | category: 'vue',
199 | componentName: componentName,
200 | // propsData: propsData,
201 | info: info
202 | });
203 |
204 | // 合并上报的数据,包括默认上报的数据和自定义上报的数据
205 | var reportData = Object.assign({}, wiierror.options);
206 | wiierror.sendReport(reportData)
207 | }
208 | },
209 | stop(){
210 | this.sendReport = function(){}
211 | },
212 | sendReport(data) {
213 | // this.report_performance()
214 | var img = new Image()
215 | img.onload = img.onerror = function() {
216 | img = null
217 | }
218 | var reqData = Object.assign({}, this.options, data, {
219 | timespan: new Date().getTime(),
220 | url: location.href
221 | })
222 | img.src = `${this.reportUrl}?${formatParams(reqData)}`
223 | },
224 | send(error, vm) {
225 | var componentName = this.formatComponentName(vm);
226 | this.options.msg = error.message ? error.message : ''
227 | this.options.stack = error.stack ? error.stack : ''
228 | this.options.data = JSON.stringify({
229 | category: 'style',
230 | componentName: componentName
231 | });
232 | this.sendReport(this.options)
233 | }
234 | }
235 |
236 | export default wiierror
--------------------------------------------------------------------------------