上一版本发布的只是单纯通过友盟监控,但是出现了错误我们也不会马上知道,毕竟友盟也没有即刻通知的功能,如果项目上线了,在测试没有覆盖到的场景下出现了线上bug,那我们开发人员也是不知道的,所以可以通过自定义一个钉钉机器人,来向群里发送报警消息,一旦项目出现了错误,直接上报到钉钉群,以便于更好的解决错误
先上报警图,然后再讲解怎么操作
这个是js错误报警,和上一篇的友盟报警类似,可以放在同一个地方进行上报,接口错误也是同理
这是接口错误报警
话不都说,直接开始
第一步:新建钉钉机器人,这个就不截图了,就是新建一个钉钉群,然后添加一个自定义机器人,但是需要注意的一点是,机器人在设置的时候,有一个安全设置,需要三选一,我都是直接选的自定义关键词,在这里需要注意,设置了自定义关键词之后,在向钉钉发送消息的时候,是需要将这个关键词包含在你发送的消息内部的,不然钉钉是接收不到你发送的信息的,群里也就不会报警
第二步:添加机器人之后,在前端项目怎么调用机器人呢?有会node的同学,可以自己写一个借口,在项目里面调用你自己的接口,通过你的接口转发,去调用钉钉机器人的 Webhook 地址。那么不会 node 的同学也不用慌,轮子也有大佬造好了,
在项目里安装 npm i dingtalk-robot-sender --save 这个模块,然后 按照官网链接的文档,一步步操作就可以了 官网链接:https://open-doc.dingtalk.com/microapp/serverapi3/iydd5h
第三步:因为需要先在本地进行调试,本地是http的,但是 Webhook 地址 是https 的,所以会包跨域,这个时候,需要去改动 webpack 配置了,不论是 cli2.xx 还是 cli3.xx 其实改动的属性都是一样的,只是改动的文件不一样,cli2.xx改动的是 build/webpack.config.dev.js 文件下的 devServer 属性,通过 proxy 配置一个代理
钉钉机器人的 Webhook 地址 https://oapi.dingtalk.com/robot/send?access_token=xxxxx 只有后面的token是不同的,所以我们在做代理的时候,可以按照下面的格式,这样在本地运行的时候,就会代理到正确地址不会报跨域了
proxy: {
'/robot/send': {
timeout: 1800000, // 请求时间
target: 'https://oapi.dingtalk.com', // 代理地址
changeOrigin: true, // 是否允许跨域,为true代表允许
secure: false // 是否允许访问 https,默认为true代表不允许,所以设置生false
},
},
相比于 cli2.xx,3.xx的架子就是大改了,有兴趣的可以看看我的另外一篇介绍2.x 和 3.x 配置的博客,在3.x的项目中,直接找到根路径下的 vue.config.js文件,也是找到 devServer 配置 proxy 代理,但是有一个 不同就是 开启 https 访问,
cli 3.x 的开启方式直接 设置属性 https:true 就可以了,secure: false 属性被移除了
下面就是一个完整的接入代码
同样是监控全局js错误,和友盟的代码可以做一个合并,同样是写在 main.js 文件内部
// 引入钉钉机器人报警
const ChatBot = require('dingtalk-robot-sender');
// 引入格式化时间,获取设备信息
import { format, initDeviceInfo, getBrowserInfo, detectOS, digits } from '../../../lib/external/config.js';
Vue.config.errorHandler = function (err, vm, info) {
// err 就是错误信息
// vm相当于this实例
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
// 只在 2.2.0+ 可用
console.log(err, vm, info, 'err, vm, info')
let errMsg = `
info: ${err}
--apName : 数据中台
--url: ${window.location.href}
--browser:${detectOS()}-${digits()} ${getBrowserInfo()}
--time: ${format('yyyy-MM-dd hh:mm:ss')}
`
// 区分上报环境
if (_czc && process.env.NODE_ENV == 'development') {
//错误捕获上报到友盟
console.log(_czc, '_czc')
_czc.push(["_trackEvent", "JS错误", "异常抛出", errMsg, 1]);
// 直接使用 webhook
const robot = new ChatBot({
webhook: '/robot/send?access_token=xxxxxxxxx'
});
// 规定发送的消息的类型和参数
let textContent = {
"msgtype": "text",
"text": {
"content": `错误:${errMsg}` // 注意了,字符串里面的错误汉字,其实就是你在钉钉报警设置的自定义字段,两个地方需要相同,否则不会发送到群里
},
}
// 机器人发送消息
robot.send(textContent)
.then((res) => {
// TODO
console.log(res)
});
}
}
这就是 config.js 文件内的几个工具函数,用来获取当前设备,浏览器信息,以及格式化时间
//避免引入新的js包,自己对formate进行简易封装
export function format(fmt) {
//author: meizz
var o = {
"M+": new Date().getMonth() + 1, //月份
"d+": new Date().getDate(), //日
"h+": new Date().getHours(), //小时
"m+": new Date().getMinutes(), //分
"s+": new Date().getSeconds(), //秒
"q+": Math.floor((new Date().getMonth() + 3) / 3), //季度
S: new Date().getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (new Date().getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt))
fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
return fmt;
};
/**
* 初始化设备信息
*/
export function initDeviceInfo() {
let _deviceInfo = ""; //设备信息
console.log(navigator, 'navigator')
if (navigator == null) {
_deviceInfo = "PC";
}
if (navigator.userAgent != null) {
var su = navigator.userAgent.toLowerCase(),
mb = ["ipad", "iphone os", "midp", "rv:1.2.3.4", "ucweb", "android", "windows ce", "windows mobile"];
// 开始遍历提前设定好的设备关键字,如果设备信息中包含关键字,则说明是该设备
for (var i in mb) {
if (su.indexOf(mb[i]) > 0) {
_deviceInfo = mb[i];
break;
}
}
}
return _deviceInfo
}
/**
* 获取浏览器的信息
*/
export function getBrowserInfo() {
var output = "other";
// Opera 8.0+
var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(" OPR/") >= 0;
if (isOpera) {
output = "Opera";
}
// Firefox 1.0+
var isFirefox = typeof InstallTrigger !== "undefined";
if (isFirefox) {
output = "Firefox";
}
// Safari 3.0+ "[object HTMLElementConstructor]"
var isSafari =
/constructor/i.test(window.HTMLElement) ||
(function (p) {
return p.toString() === "[object SafariRemoteNotification]";
})(!window["safari"] || (typeof safari !== "undefined" && safari.pushNotification));
if (isSafari) {
output = "Safari";
}
// Internet Explorer 6-11
var isIE = /*@cc_on!@*/ false || !!document.documentMode;
if (isIE) {
output = "IE";
}
// Edge 20+
var isEdge = !isIE && !!window.StyleMedia;
if (isEdge) {
output = "Edge";
}
// Chrome 1 - 79
var isChrome = !!window.chrome && navigator.userAgent.indexOf("Chrome") !== -1;
if (isChrome) {
output = "Chrome";
}
// Edge (based on chromium) detection
var isEdgeChromium = isChrome && navigator.userAgent.indexOf("Edg") !== -1;
if (isEdgeChromium) {
output = "EdgeChromium";
}
return output;
};
export function detectOS() {
var userAgent = window.navigator.userAgent,
platform = window.navigator.platform,
macosPlatforms = ["Macintosh", "MacIntel", "MacPPC", "Mac68K"],
windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"],
iosPlatforms = ["iPhone", "iPad", "iPod"],
os = null;
if (macosPlatforms.indexOf(platform) !== -1) {
os = "Mac OS";
} else if (iosPlatforms.indexOf(platform) !== -1) {
os = "iOS";
} else if (windowsPlatforms.indexOf(platform) !== -1) {
os = "Windows";
} else if (/Android/.test(userAgent)) {
os = "Android";
} else if (!os && /Linux/.test(platform)) {
os = "Linux";
}
return os;
};
export function digits() {
var sUserAgent = navigator.userAgent;
var is64 = sUserAgent.indexOf("WOW64") > -1;
if (is64) {
return "64bit";
} else {
return "32bit";
}
};
同理,对于接口报错,也可以和友盟的接口错误上报合并到一起,具体代码就不放了复制一遍过去改几个参数就好了
但是这个监控存在局限性,这个是写在项目中的,还要在项目中新增许多配置,我如果项目多了,配置起来也会很麻烦,所以我想的是,最后打包,做成 cdn 引入,只需要在项目中 调用暴露出来的 api,传当前项目名,传监控cdn的版本等等其他信息,而不用每个项目都去配置一大串。这个有待后续优化,现在只是做了一个大概的模子,要写成 cdn 模式的,需要考虑到的东西估计比较多,