背景
用户在使用electron app的时候,经常会遇到白屏,服务器异常,网络异常等情况,但是用户却不知道如何解决,所以需要提供一个可以给用户自行诊断网络的功能,一来方便用户自己快速定位问题,自行解决,二来减少开发排查问题的时间。
例如飞书在断开自动自行诊断网络状态,提示用户设置网络。
功能需求
功能需求可以参考飞书,飞书的网络诊断就是比较完整的交互例子
需求内容:
- 支持网络状态诊断 - 检测网络是否在线或离线
- 支持nds解析诊断- 检测服务器域名是否能成功解析dns
- 支持代理诊断 - 检测电脑是否开启了代理
- 服务稳定性分析 - 检测服务器域名是否可连通
功能 | 说明 | web | app |
---|---|---|---|
支持网络状态诊断 | 检测网络是否在线或离线 | ☑️ | ☑️ |
支持nds解析诊断 | 检测服务器域名是否能成功解析dns | ✖️ | ☑️ |
支持代理诊断 | 检测电脑是否开启了代理 | ✖️ | ☑️ |
服务稳定性分析 | 检测服务器域名是否可连通 | ☑️ | ☑️ |
技术选型
web技术诊断
有时候也有这样的需求,我想诊断web内所有域名服务器地址的可连通性。
js引擎(浏览器)并没有提供ping相关的能力,并且也无法通过xhr来做这种事情,因为这会涉及到跨域,那么剩下的方案就是通过资源请求,通常的做法就是发送一个img的请求,而最通用的就是请求服务器地址的favicon.ico。刚好就有这这样的一个插件可以帮助做这样的事情:ping.js
安装ping.js
$ npm install ping.js
使用demo,data返回的是rtt值
var p = new Ping();
// Using callback
p.ping("https://github.com", function(err, data) {
// Also display error if err is returned.
if (err) {
console.log("error loading resource")
data = data + " " + err;
}
document.getElementById("ping-github").innerHTML = data;
});
// You may use Promise if the browser supports ES6
p.ping("https://github.com")
.then(data => {
console.log("Successful ping: " + data);
})
.catch(data => {
console.error("Ping failed: " + data);
})
很明显这种方式对于很多场景都是不适用的,比如一个项目涉及到其他域名的请求。
app技术诊断
app是原生应用,可以调用到计算机底层的api,就算不借助插件,都可以通过打开终端,ping域名,获取返回值,所以app做这件事情是非常容易的,这里我们针对electron应用举例
诊断流程
获取域名信息
网络信息的获取分为两种方式:
1. 业务侧有输入框,用户手动输入
2. 获取项目配置的域名服务器地址
网络状态
网络问题发生时,网络状态往往是第一观察要素。如何去判断网络是否连接上了网络:
- 通用的判断方式:navigator.onLine,但是这个方式只会在机器未连接到局域网或路由器时返回false,其他情况下均返回true。 也就是说如果用户路由器本身网络就不通的情况下是没有办法判断的。
- 进阶判断方式:发出一个请求,看请求返回的状态码或者访问一个网络资源,比如图片。发出一个网络请求的网站来源,可以是一个固定稳定的静态域名服务器。
DNS解析
根据域名,查询出对应 IP 信息。服务器域名,唯有正确的解析出 IP 后,才能成功进行后续的网络连接流程,这里采用node的nds模块即可。 Desktop: dns.lookup(node)
ping连通性 与 traceRoute连通性
关于如何诊断,以及ping和traceroute的原理,可以这篇博客:https://www.cnblogs.com/seanloveslife/p/11941736.html 。
这里我们会用一个node的插件 node-net-ping 来完成这些操作,该插件不仅支持ping同时也支持traceroute,并且对这些操作的异常进行了很好的封装,简化调用过程。
如果在eletron使用过程中遇到问题:可以使用该插件node-ping,仅支持ping
下载日志
下载日志的思路就是把之前每一步诊断异常的日志通过electron-log或者其他日志插件写入到本地文件,再将本地的日志文件压缩,最后允许用户保存在指定的目录下面。
这里重点看下压缩的过程:
压缩需要用到插件archiver
const fs = require('fs')
const archiver = require('archiver')
/**
* 压缩日志
* @param {string} sourceFilePath
* @param {string} outputFilePath
* @returns
*/
const compressLogFolder = (sourceFilePath, outputFilePath) => {
return new Promise((resolve, reject) => {
try {
const outputStream = fs.createwriteStream(outputFilePath);
const archive = archiver('zip', { zlib: { level: 9 }})
archive.pipe(outputStream)
const data = fs. readFileSync (sourceFilePath, 'utf8');
archive.append(data.toString(), { name: 'network.log' })
archive.finalize();
outputStream.on( 'close', (res) = resolve(res)) ;
outputStream.on( 'error', (err) = reject(err));
} catch (error) {
reject (error)
}
})
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。