This is an automated email from the ASF dual-hosted git repository. wusheng pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/skywalking-client-js.git
commit b0560f85521523134e38e6efe36d5f5effed9a94 Author: fine <[email protected]> AuthorDate: Sat Dec 7 14:41:25 2019 +0800 feat: add monitor --- .travis.yml | 15 ++++ src/errors/ajaxErrors.ts | 63 +++++++++++++++++ src/errors/jsErrors.ts | 36 ++++++++++ src/monitor.ts | 29 +++++++- src/services/TaskQueue.ts | 33 +++++++++ src/services/baseMonitor.ts | 152 +++++++++++++++++++++++++++++++++++++++++ src/services/config.ts | 28 ++++++++ src/services/reportLog.ts | 89 ++++++++++++++++++++++++ src/services/type.d.ts | 0 src/services/utils.ts | 38 +++++++++++ src/{monitor.ts => types.d.ts} | 16 +++-- test/index.html | 26 +++++++ 12 files changed, 518 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..557edf3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: node_js + +node_js: + - "8" + +env: + matrix: + - TEST_TYPE=build + + +install: + - npm install + +script: + - npm run build diff --git a/src/errors/ajaxErrors.ts b/src/errors/ajaxErrors.ts new file mode 100644 index 0000000..6333430 --- /dev/null +++ b/src/errors/ajaxErrors.ts @@ -0,0 +1,63 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { ErrorsCategory } from '../services/config'; +export default class XHRError { + + constructor() { + this.handleError(); + } + + handleError(){ + if(!window.XMLHttpRequest){ + return; + } + let xhrSend = XMLHttpRequest.prototype.send; + let _handleEvent = (event: Event) => { + try { + if (event && event.currentTarget && event.currentTarget.status !== 200) { + // this.category = ErrorsCategory.AJAX_ERROR; + // this.msg = event.target.response; + // this.url = event.target.responseURL; + // this.error = { + // status: event.target.status, + // statusText: event.target.statusText + // }; + // this.recordError(); + } + } catch (error) { + console.log(error); + } + }; + XMLHttpRequest.prototype.send = function(){ + if (this.addEventListener){ + this.addEventListener('error', _handleEvent); + this.addEventListener('load', _handleEvent); + this.addEventListener('abort', _handleEvent); + } else { + let tempStateChange = this.onreadystatechange; + this.onreadystatechange = function(event: any){ + tempStateChange.apply(this,arguments); + if (this.readyState === 4) { + _handleEvent(event); + } + } + } + return xhrSend.apply(this,arguments); + } + } + +} \ No newline at end of file diff --git a/src/errors/jsErrors.ts b/src/errors/jsErrors.ts new file mode 100644 index 0000000..b16035f --- /dev/null +++ b/src/errors/jsErrors.ts @@ -0,0 +1,36 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +export default class JSErrors{ + + constructor() { + this.handleErrors(); + } + + private handleErrors() { + window.onerror = (msg, url, line, col, error) => { + try { + console.log('msg', msg); + console.log(url); + console.log(line); + console.log(error); + } catch(error) { + console.log("js errors",error); + } + } + } +} diff --git a/src/monitor.ts b/src/monitor.ts index c84030c..5b0b817 100644 --- a/src/monitor.ts +++ b/src/monitor.ts @@ -14,10 +14,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import JSErrors from './errors/jsErrors'; +import { TClientMonitor, TErrorsType } from './types'; export default class ClientMonitor { - public init() { - // console.log('enter'); + private errorTypes: TErrorsType = { + jsErrors: true, + promiseErrors: true, + consoleErrors: false, + vueErrors: false, + reactErrors: false, + ajaxErrors: true, + resourceErrors: true, + }; + + public init(options: TClientMonitor) { + this.errorTypes = options; + if (this.errorTypes.jsErrors) { + this.errorTypes.jsErrors = options.jsErrors; + new JSErrors(); + } + if (this.errorTypes.promiseErrors) { + this.errorTypes.promiseErrors = options.promiseErrors || this.errorTypes.promiseErrors; + } + if (this.errorTypes.resourceErrors) { + this.errorTypes.resourceErrors = options.resourceErrors; + } + if (this.errorTypes.ajaxErrors) { + this.errorTypes.ajaxErrors = options.ajaxErrors || this.errorTypes.ajaxErrors; + } } } diff --git a/src/services/TaskQueue.ts b/src/services/TaskQueue.ts new file mode 100644 index 0000000..4c8e516 --- /dev/null +++ b/src/services/TaskQueue.ts @@ -0,0 +1,33 @@ +import API from "./api.js"; + +/** + * 消息队列 + */ +var TaskQueue = { + + queues:[], //待处理消息列表 + + /** + * 添加消息 + * @param {*} reportUrl 上报url + * @param {*} data 上报数据 + */ + add:function(reportUrl,data){ + this.queues.push({reportUrl,data}); + }, + + /** + * 统一上报 + */ + fire:function(){ + if(!this.queues || this.queues.length === 0){ + return; + } + let item = this.queues[0]; + item.reportUrl && new API(item.reportUrl).report(item.data); + this.queues.splice(0,1); + this.fire(); //递归 + } +}; + +export default TaskQueue; \ No newline at end of file diff --git a/src/services/baseMonitor.ts b/src/services/baseMonitor.ts new file mode 100644 index 0000000..cd04dc9 --- /dev/null +++ b/src/services/baseMonitor.ts @@ -0,0 +1,152 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { ErrorLevelEnum,ErrorCategoryEnum } from "./config"; +import utils from "./utils"; +import TaskQueue from "./TaskQueue" + +/** + * 监控基类 + */ +class BaseMonitor { + + /** + * 上报错误地址 + * @param {*} params { reportUrl,extendsInfo } + */ + constructor(params){ + this.category = ErrorCategoryEnum.UNKNOW_ERROR; //错误类型 + this.level = ErrorLevelEnum.INFO; //错误等级 + this.msg = ""; //错误信息 + this.url = ""; //错误信息地址 + this.line = ""; //行数 + this.col = ""; //列数 + this.errorObj = ""; //错误堆栈 + + this.reportUrl = params.reportUrl; //上报错误地址 + this.extendsInfo = params.extendsInfo; //扩展信息 + } + + /** + * 记录错误信息 + */ + recordError(){ + this.handleRecordError(); + //延迟记录日志 + setTimeout(()=>{ + TaskQueue.fire(); + },100); + } + + /** + * 处理记录日志 + */ + handleRecordError(){ + try { + if(!this.msg){ + return; + } + //过滤掉错误上报地址 + if( this.reportUrl && this.url && this.url.toLowerCase().indexOf(this.reportUrl.toLowerCase())>=0 ){ + console.log("统计错误接口异常",this.msg); + return; + } + let errorInfo = this.handleErrorInfo(); + + console.log("\n````````````````````` "+this.category+" `````````````````````\n",errorInfo) + + //记录日志 + TaskQueue.add(this.reportUrl,errorInfo); + + } catch (error) { + console.log(error); + } + } + + /** + * 处理错误信息 + * @param {*} extendsInfo + */ + handleErrorInfo(){ + let txt = "错误类别: " + this.category + "\r\n"; + txt += "日志信息: " + this.msg + "\r\n"; + txt += "url: " + this.url + "\r\n"; + switch(this.category){ + case ErrorCategoryEnum.JS_ERROR: + txt += "错误行号: " + this.line + "\r\n"; + txt += "错误列号: " + this.col + "\r\n"; + if (this.errorObj && this.errorObj.stack) { + txt += "错误栈: " + this.errorObj.stack + "\r\n"; + } + break; + default: + txt += "其他错误: " + JSON.stringify(this.errorObj) + "\r\n"; + break; + } + let deviceInfo = this.getDeviceInfo(); + txt += "设备信息: " + deviceInfo; //设备信息 + let extendsInfo = this.getExtendsInfo(); + let recordInfo = extendsInfo; + recordInfo.category = this.category; //错误分类 + recordInfo.logType = this.level; //错误级别 + recordInfo.logInfo = txt; //错误信息 + recordInfo.deviceInfo = deviceInfo; //设备信息 + return recordInfo; + } + + /** + * 获取扩展信息 + */ + getExtendsInfo(){ + try { + let ret = {}; + let extendsInfo = this.extendsInfo || {}; + let dynamicParams; + if(utils.isFunction(extendsInfo.getDynamic)){ + dynamicParams = extendsInfo.getDynamic(); //获取动态参数 + } + //判断动态方法返回的参数是否是对象 + if(utils.isObject(dynamicParams)){ + extendsInfo = {...extendsInfo,...dynamicParams}; + } + //遍历扩展信息,排除动态方法 + for(var key in extendsInfo){ + if(!utils.isFunction(extendsInfo[key])){ //排除获取动态方法 + ret[key] = extendsInfo[key]; + } + } + return ret; + } catch (error) { + console.log('call getExtendsInfo error',error); + return {}; + } + } + + /** + * 获取设备信息 + */ + getDeviceInfo(){ + try { + let deviceInfo = DeviceInfo.getDeviceInfo(); + return JSON.stringify(deviceInfo); + } catch (error) { + console.log(error); + return ""; + } + } + +} +export default BaseMonitor; diff --git a/src/services/config.ts b/src/services/config.ts new file mode 100644 index 0000000..a2c420a --- /dev/null +++ b/src/services/config.ts @@ -0,0 +1,28 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +export enum ErrorsCategory { + AJAX_ERROR = 'ajaxError', + RESOURCE_ERROR = 'resourceError', + VUE_ERROR = 'vueError', + PROMISE_ERROR = 'promiseError', + JS_ERROR = 'jsError', + CONSOLE_INFO = 'consoleInfo', + CONSOLE_WARN = 'consoleWarn', + CONSOLE_ERROR = 'consoleError', + CROSS_SCRIPT_ERROR = 'crossSrciptError', + UNKNOW_ERROR = 'unknowError' +} diff --git a/src/services/reportLog.ts b/src/services/reportLog.ts new file mode 100644 index 0000000..1425f97 --- /dev/null +++ b/src/services/reportLog.ts @@ -0,0 +1,89 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +class API { + + constructor(url){ + this.url = url; + } + + /** + * 上报信息 (默认方式) + */ + report(data){ + if(!this.checkUrl(this.url)){ + console.log("上报信息url地址格式不正确,url=",this.url); + return; + } + console.log("上报地址:"+this.url); + this.sendInfo(data); + } + + /** + * 发送消息 + */ + sendInfo(data){ + try { + var xhr = new XMLHttpRequest(); + xhr.open("POST",this.url,true); + //xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.send(JSON.stringify(data)); + } catch (error) { + console.log(error); + } + } + + /** + * 通过img方式上报信息 + */ + reportByImg(data){ + if(!this.checkUrl(this.url)){ + console.log("上报信息url地址格式不正确,url=",this.url); + return; + } + try { + var img = new Image(); + img.src = this.url+'?v='+new Date().getTime()+'&' + this.formatParams(data); + } catch (error) { + console.log(error); + } + } + + /* + *格式化参数 + */ + formatParams(data) { + var arr = []; + for (var name in data) { + arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name])); + } + return arr.join("&"); + } + + /** + * 检测URL + */ + checkUrl(url){ + if(!url){ + return false; + } + var urlRule =/^[hH][tT][tT][pP]([sS]?):\/\//; + return urlRule.test(url); + } + +} +export default API; \ No newline at end of file diff --git a/src/services/type.d.ts b/src/services/type.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/services/utils.ts b/src/services/utils.ts new file mode 100644 index 0000000..cdab32d --- /dev/null +++ b/src/services/utils.ts @@ -0,0 +1,38 @@ +export default { + + type(obj) { + return Object.prototype.toString.call(obj).replace(/\[object\s|\]/g, ''); + }, + + isFunction(func) { + return this.type(func) === "Function"; + }, + + isArray(list) { + return this.type(list) === 'Array'; + }, + + /** + * 是否为null + * @param {String} str + */ + isNull(str) { + return str == undefined || str == '' || str == null; + }, + + /** + * 对象是否为空 + * @param {*} obj + */ + objectIsNull(obj) { + return JSON.stringify(obj) === "{}"; + }, + + /** + * 是否是对象 + * @param {*} obj + */ + isObject(obj){ + return this.type(obj) === "Object"; + } +} \ No newline at end of file diff --git a/src/monitor.ts b/src/types.d.ts similarity index 74% copy from src/monitor.ts copy to src/types.d.ts index c84030c..12f8276 100644 --- a/src/monitor.ts +++ b/src/types.d.ts @@ -14,10 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +export interface TClientMonitor extends TErrorsType { + reportUrl: string; +} -export default class ClientMonitor { - - public init() { - // console.log('enter'); - } +export interface TErrorsType { + jsErrors: boolean; + promiseErrors: boolean; + consoleErrors: boolean; + vueErrors: boolean; + reactErrors: boolean; + ajaxErrors: boolean; + resourceErrors: boolean; } diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..10eb0a4 --- /dev/null +++ b/test/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta http-equiv="X-UA-Compatible" content="ie=edge"> + <title>Document</title> +</head> +<body> + <div>console test</div> + <script src="../dist/monitor.js"></script> + <script> + new ClientMonitor().init({ + reportUrl: "http://baidu.com", + consoleErrors:true, + jsErrors: true, + promiseErrors: false, + vueErrors: false, + reactErrors: false, + ajaxErrors: false, + resourceErrors: false + }); + ss(); + </script> +</body> +</html> \ No newline at end of file
