對於任何的客戶端應用,開發者都但願可以在用戶上的手上記錄下相關信息以便了解真實的使用狀況。javascript
通常狀況下,分爲如下兩種信息:java
正常日誌json
在不涉及隱私的狀況下,讓開發者瞭解用戶使用客戶端的詳細狀況,從這些狀況中提煉的信息可以讓開發者根據用戶的使用狀況更好地優化產品windows
崩潰日誌api
用戶的使用環境千差萬別,有時候可能會讓客戶端崩潰。崩潰日誌的收集,有利於讓開發人員更好地定位,解決問題xcode
對於日誌的處理,通常分爲 收集 、上報、分析等多個步驟,本文主要細分講述 Electron 客戶端應用的日誌收集步驟bash
正常日誌,能夠理解成可以被開發者應用代碼正常捕獲的行爲日誌服務器
按照 Electron 應用的結構,日誌收集的結構狀況以下圖:app
解釋:框架
main process 爲主進程,render processes 爲各個窗口的渲染進程
logger 爲 main process 下的日誌收集組件,能夠把收集到的日誌信息保存到本地 和 上報到遠程服務器
Electron.remote 是 Electron 框架自帶的對象,能夠做爲主進程與渲染進程的橋接,讓渲染進程可以訪問到主進程的 logger 組件,底層原理是基於 ipc 信息的封裝
render processes 經過 Electron.remote 訪問到 logger,而後就能夠把本身的日誌信息經過 logger 進行收集
渲染進程中的 worker 和 service worker 比較特別,它們是獨立於渲染進程,沒法直接從 Electron.remote 中訪問 logger,可是它們能夠經過 ipc message 把日誌信息傳遞到特定的渲染進程,而後再經過渲染進程經過 logger 記錄日誌信息
主進程下的子進程 child processes,沒法直接訪問 Electron.remote,經過 ipc message,能夠把收集的日誌信息傳遞給主進程,主進程再經過 logger 記錄日誌信息
針對應用的需求,設置好 logger 組件
/** * @param {string} level * @param {string} text */
function log(level, text) {
var args = Array.prototype.slice.call(arguments, 1);
args = args.map(function(arg) {
return arg instanceof Error ? arg.stack + EOL : arg;
});
text = util.format.apply(util, args);
var msg = {
level: level,
text: text,
date: new Date()
};
var transports = module.exports.transports;
for (var i in transports) {
// jshint -W089
if (!transports.hasOwnProperty(i) || typeof transports[i] !== 'function') {
continue;
}
if (!compareLevels(transports[i].level, level)) {
continue;
}
try{
transports[i].call(module.exports, msg);
}catch(err){
console.error('Logger Error: ', err);
}
}
}
複製代碼
把 logger 對象暴露到全局
global.log = require('./src/base/log');
複製代碼
主進程記錄日誌
// GPU進程崩潰
app.on('gpu-process-crashed', function(){
log.error('GPU進程崩潰,程序退出');
app.exit(0);
});
// 當全部窗口被關閉了,退出
app.on('window-all-closed', function() {
// 在 OS X 上,一般用戶在明確地按下 Cmd + Q 以前
// 應用會保持活動狀態
if (process.platform != 'darwin') {
app.quit();
}
});
複製代碼
渲染進程記錄日誌
經過 remote 獲取 logger 對象
window.logger = remote.getGlobal('log');
複製代碼
在渲染進程上,把 logger 的方法與 console 方法進行綁定
function extendConsole(){
const logFn = console.log;
console.log = function(...args){
logFn(args);
if(logger && logger.log){
logger.info(args);
}
}
// ...
}
複製代碼
service worker 經過 ipc message 通信記錄日誌
向接管了Service Worker的渲染進程傳輸日誌信息
//service worker
function sendMsg(type, ...args){
self.clients.matchAll().then(
clientList => {
clientList.forEach(client => {
client.postMessage({
event: type,
data: args
});
})
}
)
}
//目標渲染進程
navigator.serviceWorker.addEventListener('message', function(swe){
// log swe.data
});
複製代碼
崩潰日誌,能夠理解爲不受開發人員應用所能控制的崩潰
開發人員的應用沒法直接捕獲這種崩潰錯誤,須要藉助框架和操做系統底層的收集機制進行日誌的收集
Electron 已經提供了崩潰日誌的收集機制,詳情能夠查看 Electron 的 官方文檔:崩潰日誌
大體流程以下圖:
解釋:
crash reporter 爲 Electron 提供的收集機制,它能夠收集應用程序沒法捕獲的崩潰錯誤,收集後,能夠把信息保存到本地或者上傳到指定服務器
主進程 與 渲染進程 的崩潰錯誤均可以被正確收集
注意點:任何進程,想要可以被收集崩潰信息,都必須在進程上顯式調用 crashReporter.start 方法,以初始化收集處理
主進程能夠直接訪問 crashReporter,渲染進程能夠經過 Electron.remote 訪問 crashReporter,子進程能夠經過 process.crashReporter 訪問崩潰收集對象
每一個進程都儘量添加上崩潰收集;
崩潰收集的信息統一上報到服務器上,方便開發人員收集統計;
以下圖(每份崩潰報告都有一個對應的 json文件 與 對應的 minidump文件):
獲取到對應的崩潰文件後,須要對 dump 文件進行分析,推薦工具爲 google 的 breakpad。關於 breakpad,不一樣平臺有不一樣的安裝方法,須要各位看官經過官方文檔仔細安裝(mac 要用 xcode 進行編譯,windows須要額外安裝 gcc)
假設已經安裝好了 breakpad,那麼咱們會有兩個工具:dump_syms 和 minidump_stackwalk,在這裏,咱們關鍵會用到 minidump_stackwalk這個工具
經過以下命令行,把 minidump 文件解析並存儲結果到 output.txt 中
minidump_stackwalk 15dcad6faa9e9914ae9016d794c391a8 > ./output.txt
複製代碼
結果以下,經過輸出的詳細信息,開發人員就能更好解決問題~