摘要:摘要通過(guò)記錄用戶行為,快速?gòu)?fù)現(xiàn)場(chǎng)景。這是搭建前端監(jiān)控系統(tǒng)的第二章,主要是介紹如何統(tǒng)計(jì)報(bào)錯(cuò),跟著我一步步做,你也能搭建出一個(gè)屬于自己的前端監(jiān)控系統(tǒng)。
摘要: 通過(guò)記錄用戶行為,快速?gòu)?fù)現(xiàn)BUG場(chǎng)景。
作者:一步一個(gè)腳印一個(gè)坑
原文:搭建前端監(jiān)控系統(tǒng)(備選)用戶行為統(tǒng)計(jì)和監(jiān)控篇(如何快速定位線上問(wèn)題)
Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。
一步一步搭建前端監(jiān)控系統(tǒng)系列博客:
一步一步搭建前端監(jiān)控系統(tǒng):JS錯(cuò)誤監(jiān)控篇
一步一步搭建前端監(jiān)控系統(tǒng):如何將網(wǎng)頁(yè)截圖上報(bào)?
一步一步搭建前端監(jiān)控系統(tǒng):接口請(qǐng)求異常監(jiān)控篇
一步一步搭建前端監(jiān)控系統(tǒng):如何定位前端線上問(wèn)題?
一步一步搭建前端監(jiān)控系統(tǒng):如何記錄用戶行為?
背景:市面上的監(jiān)控系統(tǒng)有很多,大多收費(fèi),對(duì)于小型前端項(xiàng)目來(lái)說(shuō),必然是痛點(diǎn)。另一點(diǎn)主要原因是,功能雖然通用,卻未必能夠滿足我們自己的需求, 所以我們自給自足也許是個(gè)不錯(cuò)的辦法。
這是搭建前端監(jiān)控系統(tǒng)的第二章,主要是介紹如何統(tǒng)計(jì)js報(bào)錯(cuò),跟著我一步步做,你也能搭建出一個(gè)屬于自己的前端監(jiān)控系統(tǒng)。
目前已經(jīng)在運(yùn)行的線上Demo:前端監(jiān)控系統(tǒng)
代碼和講解都放在這篇文章里:監(jiān)控系統(tǒng)介紹及代碼
如果實(shí)在嫌部署麻煩,Demo系統(tǒng)可以提供 7天 的監(jiān)控量,我會(huì)長(zhǎng)期維護(hù):一鍵部署
一直以來(lái), 前端上線的項(xiàng)目,對(duì)于前端程序猿來(lái)說(shuō),完全是一個(gè)黑盒子。 項(xiàng)目一旦上線,我們完全不知道用戶在我們的項(xiàng)目里邊做了什么,跳轉(zhuǎn)到哪里,是不是報(bào)錯(cuò)了。一旦線上用戶出現(xiàn)問(wèn)題,而我們又無(wú)法復(fù)現(xiàn)的時(shí)候,才能體會(huì)到什么叫絕望。 不管多么艱難,問(wèn)題總是會(huì)在哪里等著你。所以,如果我們可以把線上的項(xiàng)目變成一個(gè)白盒子,讓我們能夠知道用戶在線上干了什么,復(fù)現(xiàn)不再困難了,對(duì)前端程序員來(lái)說(shuō),是不是一件好事呢。
接下來(lái)我要寫的是一個(gè)重要的功能, 因?yàn)樗鼧O大的提高了我解決問(wèn)題的能力, 也讓對(duì)我的工作產(chǎn)生了很大的影響。
截止到現(xiàn)在,來(lái)看看我已經(jīng)完成了哪些功能:
PV/UV的統(tǒng)計(jì)上報(bào),js錯(cuò)誤的上報(bào)和分析, 接口的統(tǒng)計(jì)上報(bào),頁(yè)面截屏的統(tǒng)計(jì)上報(bào)。 那么,再補(bǔ)上今天要寫的“用戶點(diǎn)擊行為的上報(bào)”, 我們基本上就能夠分析出一個(gè)用戶在頁(yè)面上干了什么。
一、如何記錄線上用戶的行為線上用戶的基本行為包括: 訪問(wèn)頁(yè)面, 點(diǎn)擊行為,請(qǐng)求接口行為, js報(bào)錯(cuò)行為, 這幾點(diǎn)基本上能夠清楚的記錄下用戶在線上的所有行為。 當(dāng)然還包括:資源加載行為,滾動(dòng)頁(yè)面行為, 元素進(jìn)入用戶視野等等行為,這些是更為細(xì)節(jié)的行為統(tǒng)計(jì), 也許會(huì)在以后進(jìn)行完善, 但是以上的四種行為已經(jīng)可以完成我們的統(tǒng)計(jì)需求。
訪問(wèn)頁(yè)面, js報(bào)錯(cuò)行為我們已經(jīng)有了,接下來(lái)看看如何統(tǒng)計(jì)點(diǎn)擊行為和請(qǐng)求接口的行為吧。
點(diǎn)擊行為// 用戶行為日志,繼承于日志基類MonitorBaseInfo function BehaviorInfo(uploadType, behaviorType, className, placeholder, inputValue, tagName, innerText) { setCommonProperty.apply(this); this.uploadType = uploadType; this.behaviorType = behaviorType; this.className = utils.b64EncodeUnicode(className); this.placeholder = utils.b64EncodeUnicode(placeholder); this.inputValue = utils.b64EncodeUnicode(inputValue); this.tagName = tagName; this.innerText = utils.b64EncodeUnicode(encodeURIComponent(innerText)); } /** * 用戶行為記錄監(jiān)控 * @param project 項(xiàng)目詳情 */ function recordBehavior(project) { // 行為記錄開(kāi)關(guān) if (project && project.record && project.record == 1) { // 記錄行為前,檢查一下url記錄是否變化 checkUrlChange(); // 記錄用戶點(diǎn)擊元素的行為數(shù)據(jù) document.onclick = function (e) { var className = ""; var placeholder = ""; var inputValue = ""; var tagName = e.target.tagName; var innerText = ""; if (e.target.tagName != "svg" && e.target.tagName != "use") { className = e.target.className; placeholder = e.target.placeholder || ""; inputValue = e.target.value || ""; innerText = e.target.innerText.replace(/s*/g, ""); // 如果點(diǎn)擊的內(nèi)容過(guò)長(zhǎng),就截取上傳 if (innerText.length > 200) innerText = innerText.substring(0, 100) + "... ..." + innerText.substring(innerText.length - 99, innerText.length - 1); innerText = innerText.replace(/s/g, ""); } var behaviorInfo = new BehaviorInfo(ELE_BEHAVIOR, "click", className, placeholder, inputValue, tagName, innerText); behaviorInfo.handleLogInfo(ELE_BEHAVIOR, behaviorInfo); } } };
我們先來(lái)看一下點(diǎn)擊行為的代碼,其實(shí)很簡(jiǎn)單,就是重寫一下document的onclick方法,然后把相應(yīng)的元素的屬性,內(nèi)容等等保存起來(lái), 但是,我們費(fèi)了這么大的力氣保存了如此多的日志,就為了簡(jiǎn)單的記錄一下用戶的點(diǎn)擊行為,實(shí)在太浪費(fèi)了。 所以,這個(gè)點(diǎn)擊行為統(tǒng)計(jì)會(huì)被添加到未來(lái)的留存分析當(dāng)中去,到時(shí)候能夠?qū)崿F(xiàn)無(wú)埋點(diǎn)記錄日志的功能,讓我們的監(jiān)控系統(tǒng)更加的強(qiáng)大和豐富。留存分析會(huì)參考GrowingIo, 有興趣可以了解一下。
我們需要記錄下元素的className, tagName, innerText等等,我們需要足夠的的內(nèi)容才能夠確定用戶點(diǎn)擊的是哪個(gè)按鈕。這種方式比較弱智,將會(huì)在以后寫留存分析功能的時(shí)候進(jìn)行完善一下,但是目前足以滿足我們的要求了。
請(qǐng)求接口行為// 接口請(qǐng)求日志,繼承于日志基類MonitorBaseInfo function HttpLogInfo(uploadType, url, status, statusText, statusResult, currentTime) { setCommonProperty.apply(this); this.uploadType = uploadType; this.httpUrl = utils.b64EncodeUnicode(url); this.status = status; this.statusText = statusText; this.statusResult = statusResult; this.happenTime = currentTime; } /** * 頁(yè)面接口請(qǐng)求監(jiān)控 */ function recordHttpLog() { // 監(jiān)聽(tīng)ajax的狀態(tài) function ajaxEventTrigger(event) { var ajaxEvent = new CustomEvent(event, { detail: this }); window.dispatchEvent(ajaxEvent); } var oldXHR = window.XMLHttpRequest; function newXHR() { var realXHR = new oldXHR(); realXHR.addEventListener("abort", function () { ajaxEventTrigger.call(this, "ajaxAbort"); }, false); realXHR.addEventListener("error", function () { ajaxEventTrigger.call(this, "ajaxError"); }, false); realXHR.addEventListener("load", function () { ajaxEventTrigger.call(this, "ajaxLoad"); }, false); realXHR.addEventListener("loadstart", function () { ajaxEventTrigger.call(this, "ajaxLoadStart"); }, false); realXHR.addEventListener("progress", function () { ajaxEventTrigger.call(this, "ajaxProgress"); }, false); realXHR.addEventListener("timeout", function () { ajaxEventTrigger.call(this, "ajaxTimeout"); }, false); realXHR.addEventListener("loadend", function () { ajaxEventTrigger.call(this, "ajaxLoadEnd"); }, false); realXHR.addEventListener("readystatechange", function() { ajaxEventTrigger.call(this, "ajaxReadyStateChange"); }, false); return realXHR; } window.XMLHttpRequest = newXHR; window.addEventListener("ajaxLoadStart", function(e) { var currentTime = new Date().getTime() setTimeout(function () { var url = e.detail.responseURL; var status = e.detail.status; var statusText = e.detail.statusText; if (!url || url.indexOf(HTTP_UPLOAD_LOG_API) != -1) return; var httpLogInfo = new HttpLogInfo(HTTP_LOG, url, status, statusText, "發(fā)起請(qǐng)求", currentTime); httpLogInfo.handleLogInfo(HTTP_LOG, httpLogInfo); }, 2000) }); window.addEventListener("ajaxLoadEnd", function(e) { var currentTime = new Date().getTime() var url = e.detail.responseURL; var status = e.detail.status; var statusText = e.detail.statusText; if (!url || url.indexOf(HTTP_UPLOAD_LOG_API) != -1) return; var httpLogInfo = new HttpLogInfo(HTTP_LOG, url, status, statusText, "請(qǐng)求返回", currentTime); httpLogInfo.handleLogInfo(HTTP_LOG, httpLogInfo); }); }
讓我們來(lái)看看接口行為統(tǒng)計(jì)的代碼先,本來(lái)這個(gè)我想多帶帶拿出來(lái)說(shuō)一說(shuō)的,但是現(xiàn)在么有那么多時(shí)間把它相關(guān)的功能開(kāi)發(fā)出來(lái),所以只寫了一個(gè)簡(jiǎn)版的。
接口行為的統(tǒng)計(jì)包括: 發(fā)起請(qǐng)求,接收請(qǐng)求,接收狀態(tài),請(qǐng)求時(shí)長(zhǎng), 通過(guò)前端對(duì)接口的統(tǒng)計(jì)和分析,我們是可以觀察出線上接口的質(zhì)量,同時(shí)也能夠?qū)η岸说倪壿嬜龀鱿鄳?yīng)的調(diào)整,已達(dá)到頁(yè)面加載的最佳效果。 數(shù)據(jù)庫(kù)字段定義都在分析后臺(tái)的項(xiàng)目里, 可以直接去看。
首先,我們要監(jiān)聽(tīng)頁(yè)面的ajax請(qǐng)求, 如上所示,寫了一段監(jiān)聽(tīng)ajax請(qǐng)求的代碼(我是在網(wǎng)上扒下來(lái)的 thanks), 可以監(jiān)聽(tīng)到頁(yè)面上所有的ajax請(qǐng)求,對(duì)整個(gè)ajax請(qǐng)求過(guò)程進(jìn)行了原子性分析,我們可以監(jiān)聽(tīng)到請(qǐng)求過(guò)程中任何一個(gè)時(shí)段的事件,非常好用。 但是,有一點(diǎn)非常重要, 如果你的項(xiàng)目里邊用的是fetch請(qǐng)求數(shù)據(jù)的話, 那么這些監(jiān)聽(tīng)就無(wú)效了。 因?yàn)閒etch代碼是瀏覽器注入的, 肯定先用監(jiān)控代碼執(zhí)行,然后你再監(jiān)聽(tīng)ajax就一點(diǎn)用都沒(méi)有了。 所以你需要在寫好ajax監(jiān)聽(tīng)之后,重寫fetch代碼, 這樣就可以生效了。好了,這部分并不是這篇幅的重點(diǎn),我們就說(shuō)到這里。
二、如何查詢線上用戶的行為終于,我們把剩下的兩種行為記錄都成功上傳了,那么該如果把他們都查詢出來(lái)呢。我們先來(lái)看一下頁(yè)面上我查詢出來(lái)的結(jié)果。
因?yàn)槠聊惶。瑹o(wú)法展示所有的記錄,記錄信息包含:行為名稱,行為發(fā)生時(shí)間, 行為發(fā)生頁(yè)面, 錯(cuò)誤信息, 錯(cuò)誤截圖, 以及用戶自定義上傳截圖的時(shí)機(jī)。
說(shuō)到這里有幾個(gè)小問(wèn)題需要注意。
因?yàn)槭怯肑s做探針,記錄日志的時(shí)候很難保證每次記錄都可以把用戶的userId插入進(jìn)去
所以我們給每個(gè)用戶都定義一個(gè)customerKey來(lái)做區(qū)分,如果用戶不卸載app和清理app的緩存, customerKey將保持不變
在查詢用戶的行為記錄的時(shí)候,需要先查詢出用戶所有的customerKey(可能有多個(gè)),再用customerKey進(jìn)行查詢,便可以得到準(zhǔn)確的結(jié)果。
三、如何分析線上用戶的行為其實(shí)我們做了這么多,記錄了這么多,就是為了這個(gè)目的:分析行為,快速定位問(wèn)題。
那么我們?nèi)绾味ㄎ粏?wèn)題呢,我可以舉例說(shuō)明一下:
JS報(bào)錯(cuò)阻斷行為,我們可以看到發(fā)生錯(cuò)誤的前后行為,就能夠快速準(zhǔn)確定位問(wèn)題。
復(fù)雜的鏈接跳轉(zhuǎn)發(fā)生了錯(cuò)誤。有些錯(cuò)誤是前端頁(yè)面會(huì)經(jīng)過(guò)復(fù)雜的跳轉(zhuǎn),回退之后才發(fā)生的,就算測(cè)試人員也很難測(cè)試出這種問(wèn)題,因?yàn)榫€上的用戶的任何行為都有可能出現(xiàn)。往往我們知道的只是他在最后停留的頁(yè)面發(fā)生了錯(cuò)誤。 如此,經(jīng)過(guò)我們排查行為日志, 就能夠復(fù)現(xiàn)出用戶的行為, 從而復(fù)現(xiàn)BUG
接口異常。 正常情況下,前端的接口都會(huì)設(shè)置超時(shí)時(shí)間的, 但是呢, 后臺(tái)接口排查發(fā)現(xiàn)正常, 而前端就是無(wú)法正常執(zhí)行, 這種問(wèn)題沒(méi)有顯示的錯(cuò)誤現(xiàn)象,而線上的反饋并不能夠準(zhǔn)確,前端只能背鍋了。 而日志記錄是可以把請(qǐng)求發(fā)出時(shí)間和返回時(shí)間記錄下來(lái), 是否超時(shí),看一眼就知道。
線上的用戶根本就不會(huì)反饋異常, 他們能做的只是把最后一眼能看到的東西告訴你。 天知道他們之前經(jīng)歷了什么步驟。 最終的結(jié)果是,前端有問(wèn)題,然后背鍋,哈哈。
總之, 我們知道用戶在頁(yè)面上干了什么, 便不再擔(dān)心問(wèn)題出現(xiàn), 遇見(jiàn)問(wèn)題也不會(huì)再手忙腳亂了。
關(guān)于FundebugFundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java線上應(yīng)用實(shí)時(shí)BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計(jì)處理了10億+錯(cuò)誤事件,付費(fèi)客戶有陽(yáng)光保險(xiǎn)、核桃編程、荔枝FM、掌門1對(duì)1、微脈、青團(tuán)社等眾多品牌企業(yè)。歡迎大家免費(fèi)試用!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/106358.html
摘要:一直以來(lái),前端的線上問(wèn)題很難定位,因?yàn)樗l(fā)生于用戶的一系列操作之后。當(dāng)然,這些問(wèn)題并非不能克服,讓我們來(lái)一起看看如何去定位線上的問(wèn)題吧。地址參考一步一步搭建前端監(jiān)控系統(tǒng)錯(cuò)誤監(jiān)控篇一步一步搭建前端監(jiān)控系統(tǒng)接口請(qǐng)求異常監(jiān)控篇 摘要: 記錄用戶行為,排查線上BUG。 作者:一步一個(gè)腳印一個(gè)坑 原文:如何定位前端線上問(wèn)題(如何排查前端生產(chǎn)問(wèn)題) Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所...
摘要:目前已經(jīng)在運(yùn)行的線上前端監(jiān)控系統(tǒng)代碼和講解都放在這篇文章里監(jiān)控系統(tǒng)介紹及代碼用戶對(duì)前端程序員來(lái)說(shuō),就是一個(gè)黑匣子。 摘要: 通過(guò)錄屏或者截圖,快速?gòu)?fù)現(xiàn)BUG場(chǎng)景。 作者:一步一個(gè)腳印一個(gè)坑 原文:搭建前端監(jiān)控系統(tǒng)(備選)Js截圖上報(bào)篇 Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。 PS:本文關(guān)于Fundebug錄屏功能的內(nèi)容有些不準(zhǔn)確的地方,比如錄屏并非通過(guò)截圖實(shí)現(xiàn)的,錄屏插件...
摘要:摘要徒手寫錯(cuò)誤監(jiān)控。為什么用定時(shí)器呢,因?yàn)樵趩雾?yè)應(yīng)用中,路由的切換和地址欄的變化是無(wú)法被監(jiān)控的,我確實(shí)沒(méi)有想到特別好的辦法來(lái)監(jiān)控,所以用了這種方式,如果有人有更好的辦法,請(qǐng)給我留言,謝謝。 摘要: 徒手寫JS錯(cuò)誤監(jiān)控。 作者:一步一個(gè)腳印一個(gè)坑 原文:搭建前端監(jiān)控系統(tǒng)(二)JS錯(cuò)誤監(jiān)控篇 Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。 背景:市面上的監(jiān)控系統(tǒng)有很多,大多收費(fèi),對(duì)于...
摘要:參考一步一步搭建前端監(jiān)控系統(tǒng)錯(cuò)誤監(jiān)控篇用插件記錄網(wǎng)絡(luò)請(qǐng)求異常關(guān)于專注于微信小程序微信小游戲支付寶小程序和線上應(yīng)用實(shí)時(shí)監(jiān)控。 摘要: 如何監(jiān)控HTTP請(qǐng)求錯(cuò)誤? 作者:一步一個(gè)腳印一個(gè)坑 原文:搭建前端監(jiān)控系統(tǒng)(四)接口請(qǐng)求異常監(jiān)控篇 Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。 背景:市面上的監(jiān)控系統(tǒng)有很多,大多收費(fèi),對(duì)于小型前端項(xiàng)目來(lái)說(shuō),必然是痛點(diǎn)。另一點(diǎn)主要原因是,功能雖然...
摘要:只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙只有動(dòng)手,你才能真正掌握一門技術(shù)持續(xù)更新中項(xiàng)目地址求求求源碼系列跟一起學(xué)如何寫函數(shù)庫(kù)中高級(jí)前端面試手寫代碼無(wú)敵秘籍如何用不到行代碼寫一款屬于自己的類庫(kù)原理講解實(shí)現(xiàn)一個(gè)對(duì)象遵循規(guī)范實(shí)戰(zhàn)手摸手,帶你用擼 Do it yourself!!! 只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙 只有動(dòng)手,你才能真正掌握一門技術(shù) 持續(xù)更新中…… 項(xiàng)目地址 https...
閱讀 741·2021-11-24 10:19
閱讀 1128·2021-09-13 10:23
閱讀 3446·2021-09-06 15:15
閱讀 1788·2019-08-30 14:09
閱讀 1704·2019-08-30 11:15
閱讀 1852·2019-08-29 18:44
閱讀 950·2019-08-29 16:34
閱讀 2470·2019-08-29 12:46