Vue3+vite+sls打造前端性能監(jiān)控平臺(tái)

基于vue3+vite+阿里云sls開(kāi)發(fā)的一個(gè)簡(jiǎn)單的性能監(jiān)控平臺(tái),可以進(jìn)行用戶行為分析,如:用戶數(shù)量,熱點(diǎn)頁(yè)面訪問(wèn)量,訪問(wèn)時(shí)段,用戶瀏覽器使用,分辨率等等數(shù)據(jù),以及前端性能相關(guān)數(shù)據(jù),如:頁(yè)面加載時(shí)間,js報(bào)錯(cuò)相關(guān)信息,接口加載時(shí)間,接口調(diào)用次數(shù)等等。下面廢話不多說(shuō),直接開(kāi)始項(xiàng)目。
1.vue3+vite初始化項(xiàng)目

1.   npm create vite 項(xiàng)目名稱
2.   cd項(xiàng)目名稱
3.   npm install    《安裝項(xiàng)目依賴包》
4.   npm run dev   《啟動(dòng)項(xiàng)目》

選擇框架的時(shí)候選擇vue+ts,完成之后就獲得了一個(gè)基礎(chǔ)版本的項(xiàng)目。大致目錄如下:


微信截圖_20220325133337.png

你會(huì)發(fā)現(xiàn)script用到了setup語(yǔ)法糖,


微信截圖_20220325134731.png

第一次見(jiàn)到的小伙伴可能會(huì)一臉懵,這里先附上官網(wǎng)文檔以及一份寫的不錯(cuò)的setup語(yǔ)法糖,總結(jié)就是變量不用return了,組件不用注冊(cè)了,props和emits換方法了,父組件要調(diào)用子組件內(nèi)容,需要把子組件變量或者方法defineExpose({})出去,等等。
https://v3.cn.vuejs.org/api/sfc-script-setup.html#%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95
https://mp.weixin.qq.com/s/-1VRzekOKvK4ymEN8mHAFg
vue3不再使用elementui,而是改成了element-plus,這點(diǎn)大家也要注意,附上官網(wǎng):
https://element-plus.gitee.io/zh-CN/
element-plus自身支持國(guó)際化,有點(diǎn)比較麻煩的是里面的icon需要用到哪個(gè),就要在文件里import哪個(gè),沒(méi)有原來(lái)方便了,可以使用注冊(cè)全局組件的方法,把所有ICON注入:

import * as Icons from '@element-plus/icons'
Object.keys(Icons).forEach(key => {
  app.component(key, Icons[key as keyof typeof Icons])
})

以上這些只是隨意提一嘴,屬于基礎(chǔ)內(nèi)容,打造前端性能監(jiān)控平臺(tái),最主要的在于阿里云sls日志服務(wù)的使用。

微信截圖_20220329201937.png

也就是這個(gè)東西,開(kāi)通服務(wù),然后新建Project:
微信截圖_20220329203139.png

然后再新建一個(gè)Logstore
微信截圖_20220329203222.png

此時(shí)切記一定要打開(kāi)WebTracking,這是專門為前端來(lái)采集信息的。最好打開(kāi)永久保存。
創(chuàng)建完畢之后會(huì)問(wèn)你是否立即接入數(shù)據(jù),選擇是,然后選擇webTracking接入數(shù)據(jù):
微信截圖_20220329204229.png

微信截圖_20220329204238.png

接入之后,阿里云的基本配置也就準(zhǔn)備好了,下一步就是去我們的項(xiàng)目里進(jìn)行埋點(diǎn),并且使用阿里云的WebTracking SDK將埋點(diǎn)數(shù)據(jù)傳到阿里云sls上。
這里附上官方文檔地址:
https://help.aliyun.com/document_detail/31752.html?spm=5176.2020520112.0.0.377f34c0q0DTnF#h4--http-get-
微信截圖_20220402094906.png

這里我們嘗試上傳一個(gè)js埋點(diǎn)報(bào)錯(cuò)數(shù)據(jù):

//監(jiān)聽(tīng)頁(yè)面Js報(bào)錯(cuò)
function injectJsError(){
  window.addEventListener('error',function(event){
    console.log('------addEventListener-error-------')
    console.log(event)
    let log = {
      kind:'stability',//監(jiān)控指標(biāo)大類 穩(wěn)定性
      type:'error',//小類
      errorType:'jsError',
      message:event.message,
      filename:event.filename,
      position:`${event.lineno}:${event.colno}`,
      stack:event.error&&event.error.stack?getLines(event.error.stack):'-'
    }
    SentTracker.sendMsg(log);
    // console.log(log)
  })
}
class SentTracker{
  url;
  xhr;
  constructor(){
    this.url = `https://${ProjectName}.${Endpoint}/logstores/${logstoreName}/track`;
    this.xhr = new XMLHttpRequest;
  }
  //上報(bào)單條埋點(diǎn)信息
  sendMsg(data = {}){
    let extraData = getExtraData();
    let logInfo = {...extraData,...data}
    for(let key in logInfo){
      if(typeof logInfo[key] === 'number'){ //如果是數(shù)字 需要轉(zhuǎn)為字符串,阿里要求的傳參格式
        logInfo[key] = `${logInfo[key]}`
      }
    }
    logger.send(logInfo)
  }
  //上報(bào)多條埋點(diǎn)信息
  sendMsgs(data = []){
    let extraData = getExtraData();
    let logArr= [logInfo]
    data.forEach((item)=>{
      let logInfo = {...extraData,...item}
      for(let key in item){
        if(typeof item[key] === 'number'){ //如果是數(shù)字 需要轉(zhuǎn)為字符串,阿里要求的傳參格式
          item[key] = `${item[key]}`
        }
      }
      logArr.push(logInfo)
    })
    let body = JSON.stringify(logInfo)
    this.xhr.open('POST',this.url,true);
    this.xhr.setRequestHeader('Content-Type','application/json');
    this.xhr.setRequestHeader('x-log-apiversion','0.6.0');
    this.xhr.setRequestHeader('x-log-bodyrawsize',body.length);
    this.xhr.setRequestHeader('x-log-compresstyp','lz4');
    this.xhr.onload = ()=>{
      console.log(this.xhr.response)
    }
    this.xhr.onerror = (error)=>{
      console.log(error)
    }
    let newBody = {
      "__topic__": topic,
      "__source__": source,
      "__logs__":logArr,
      "__tags__": {
        "tag": "日志標(biāo)簽"
      }
    }
    this.xhr.send(JSON.stringify(newBody));
  }
}

當(dāng)監(jiān)聽(tīng)到j(luò)s報(bào)錯(cuò)的時(shí)候,項(xiàng)目就會(huì)自動(dòng)把錯(cuò)誤信息上傳到sls里


微信截圖_20220402101409.png

當(dāng)時(shí)我在想,如果我要統(tǒng)計(jì)后端接口的調(diào)用次數(shù),那是不是每次調(diào)用接口都要往sls里發(fā)起一次請(qǐng)求,那對(duì)于我項(xiàng)目的性能不是會(huì)有很大影響么,但是sls其實(shí)也早就想到了這點(diǎn),所以它有兩個(gè)配置項(xiàng):

opts = {
    host: '',      
    project: '',                 
    logstore: '',               
    time: 10, //10s內(nèi)自動(dòng)收集日志,10s發(fā)一次
    count: 30, //最多收集30條
  }

可以看到,他是可以自動(dòng)收集埋點(diǎn)數(shù)據(jù),然后每隔一段時(shí)間發(fā)起一次請(qǐng)求的。
我們?cè)偃タ纯窗⒗镌迫罩編?kù):


微信截圖_20220402102002.png

所有上傳的埋點(diǎn)信息,都會(huì)存進(jìn)去,可以理解為一張數(shù)據(jù)庫(kù)表,下一步,就是把這些表數(shù)據(jù)收集整理,然后做成可視化圖表就行了,只要會(huì)簡(jiǎn)單的sql語(yǔ)句就行。
這里有個(gè)坑,我當(dāng)時(shí)按照文檔用Js去查日志庫(kù)里的數(shù)據(jù),死活查不出來(lái),后來(lái)才發(fā)現(xiàn),必須用node.js去查才可以,這里附上node關(guān)鍵代碼:

const ALY = require('aliyun-sdk')
var sls = new ALY.SLS({
  accessKeyId: "",                         //阿里云訪問(wèn)密鑰AccessKey ID。更多信息,請(qǐng)參見(jiàn)訪問(wèn)密鑰。阿里云賬號(hào)AccessKey擁有所有API的訪問(wèn)權(quán)限,風(fēng)險(xiǎn)很高。強(qiáng)烈建議您創(chuàng)建并使用RAM用戶進(jìn)行API訪問(wèn)或日常運(yùn)維。 
  secretAccessKey: "",                     //阿里云訪問(wèn)密鑰AccessKey Secret。 
  endpoint: '', //日志服務(wù)的域名。更多信息,請(qǐng)參見(jiàn)服務(wù)入口。此處以杭州為例,其它地域請(qǐng)根據(jù)實(shí)際情況填寫。
  apiVersion: '2015-06-01'                         //SDK版本號(hào),固定值。
})
const projectName = ""               // 必選,Project名稱。
const logstoreName = ""             // 必選,Project描述。
// 查詢?nèi)罩尽?function queryLog(query, callback) {
  const param = {
    projectName,                                  // 必選,Project名稱。
    logStoreName: logstoreName,                   // 必選,Logstore名稱。
    from: query.from,                                         // 必選,開(kāi)始時(shí)間,精度為秒。
    to: query.to,                                           // 必選,結(jié)束時(shí)間,精度為秒
    topic: query.topic,                                    // 可選,指定日志主題。
    query: query.query                                   // 可選,查詢的關(guān)鍵詞,不輸入則查詢?nèi)咳罩緮?shù)據(jù)。
  }

  sls.getLogs(param, function (err, data) {
    if (err) {
      let obj = {
        code: 500,
        data: '',
        msg: err
      }
      callback(obj)
    } else {
      let obj = {
        code: 0,
        data: data,
        msg: 'success'
      }
      callback(obj)
    }
  })
}

然后通過(guò)前端調(diào)用后端Node的接口,前端就可以獲取到日志庫(kù)里面的數(shù)據(jù)了,node只是為了前端獲取數(shù)據(jù)做一道中轉(zhuǎn)而已。附上前端sql語(yǔ)句的示例

 //獲取js執(zhí)行報(bào)錯(cuò)統(tǒng)計(jì)
  let jsError = await getLogs(
    proxy.$urlData.getLogs,
    "* and kind : stability and type : error and errorType : jsError | SELECT message,url, count(url) AS PV where message!='null' GROUP BY message,url ORDER BY PV DESC"
  );

此時(shí)一個(gè)性能監(jiān)控平臺(tái)就搭建好了:


微信截圖_20220402161616.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容