玩命加载中 . . .

【前端监控系统开发实录】之原生 SDK 开发(三)


在之前的文章我们已经理清 SDK 的开发流程:

  1. 明确采集数据
  2. 封装采集方法
  3. 数据上报后端
  4. 接入项目测试

那么本文就来看看怎么处理最后两点。

数据上报后端

开放后端接口

后端的技术栈为 nodejs + koa + influxdb + mysql ,因此在入口文件 app.ts 中开放如下接口(这里以 前端异常 为例):
后端开放的接口

  • 上报异常数据的接口为 /report/xxx ,method 为 POST
  • 请求异常数据的接口为 /api/xxx ,method 为 GET

调用接口上报数据

上一篇文章 我们已经封装好了捕获异常的方法,但还缺少了数据的上报功能,下面以 js 异常为例展示如何上报数据。

这是捕获 js 异常的函数:

window.addEventListener('error', (event) => {
    handleJs(event);
}, true)

const handleJs = function (event: any): void {
    event.preventDefault();

    // 用户最后一个交互事件
    const lastEvent: Event = getLastEvent();
    let log = null;

    // 判断报错种类
    const type = getErrorKey(event);

    if (type === mechanismType.JS) {
        log = {
            message: event.message,
            type: event.type,
            errorType: `${mechanismType.JS}: ${(event.error && event.error.name) || 'UnKnowun'}`,
            fileName: event.filename,
            position: `${event.lineno}:${event.colno}`,
            // stack: getLines(event.error.stack), //错误堆栈
            selector: lastEvent ? getSelector((lastEvent as any).path) : '',
        }
        console.log('jsError log数据', log)
        
        // 在这里调用上报的函数
        lazyReport('/js', log);
    }
}

lazyReport 方法详解:

  • 第一个参数:string,表示后端的接口路径。
  • 第二个参数:object,表示要上传的数据。

Q:不是说 上报异常数据的接口为 /report/xxx 吗,那例子中的 js 异常数据上报接口为什么是 /js ,而不是 /report/js

A:由于在 SDK 中需要调用后端接口的只有异常数据的上报功能,因此这里做了封装,/report 写在了后端地址里,后面会讲到。

lazyReport 方法封装:

先全局定义:

// --------- 服务端地址 ----------------
if (reportUrl) {
    window['_monitor_report_url_'] = reportUrl;
}

// -------- 合并上报的间隔 ------------
if (delay) {
    window['_monitor_delay_'] = delay;
}

在 lazyReport 中使用:

export function lazyReport(interfaceUrl: string, param: object): void {
    const delay: number = window['_monitor_delay_'];
    let data = JSON.stringify(param);
    if (delay === 0) {
        return report(interfaceUrl, data);
    }
}

export function report(interfaceUrl: string, data: object): void {
  const url = window['_monitor_report_url_'];

  // ------- navigator/img方式上报,不会有跨域问题 -------
  // 支持sendBeacon的浏览器
  if (navigator.sendBeacon) { 
    // url: 'https://localhost:8080/report'
    // interfaceUrl: '/js'
    navigator.sendBeacon(url + interfaceUrl, JSON.stringify(data));
  } else { // 不支持sendBeacon的浏览器
      let oImage = new Image();
      oImage.src = `${url}${interfaceUrl}?logs=${data}`;
  }
}

这里使用 Navigator.sendBeacon() 发送请求,原因如下:

  • 当浏览器中发生终止页面的情况时,并不能保证进程内的HTTP请求会成功,也就是说如果我们关闭页面后还有异常数据没来得及上报,那么它将不能成功上报。
  • SDK 中不需要复杂的服务请求,只要确保监控到的异常数据能成功上报即可,不需要进行太多定制。

需要注意的是,使用 Navigator.sendBeacon() 的话,我们需要在后端进行适当的处理,才能获取到传过去数据,这在下面会讲到。

后端中间件逻辑

为了提高表数据的查询性能,后端使用了时序数据库 influxdb 来达到效果:
【前端监控系统开发实录】之时序数据库的探索

需要注意的是:

  1. 注册中间件(一般在 app.js 里面)的时候在 koa-bodyparser 前加上一个中间件,让 bodyparser 不要处理这个路由请求,自己来处理这条特别的请求:
app.use(async function(ctx,next) {
    //判断请求的路由路径
    if (/^.*/beacon/.+$/.test(ctx.path)) {
        ctx.disableBodyParser = true;
    }
    await next();
})
// 处理 post 请求的参数
app.use(bodyParser());

// js 相关
router.post('/report/js', addJs);
  1. 调用 /report/js 接口后会进入 addJs 处理层,在里面引入 co-body 插件:npm install co-body --save
    const coBody = require('co-body');
  2. 用 json 的方式格式化这个请求数据
    const params = await coBody.json(ctx.req);

接入项目测试

我们的项目结构是这样子的:

SDK 文件结构图

可以知道,在 SDK 的 example 目录下是我们自己搭建的 vue 项目,用于测试异常是否能正确捕获,以及是否接入成功。

那么测试项目是怎么接入 SDK 的呢?请看:

根据实际开发步骤,这时候其实还没发布 npm 包,因此先采用本地引入 SDK 的方式测试是否能成功接入。

  1. SDK 的入口文件暴露如下配置:

    import { loadConfig } from './common/loadConfig';
    import { errorCatch } from './common/errorTrack';
    import { initOptions } from "./type";
    
    function init(options: initOptions) {
        // ------- 加载配置 ----------
        // 1.拿到配置信息
        // 2.注入监控代码
        loadConfig(options);
    }
    
    export { init, errorCatch, };

    loadConfig 函数的具体内容可以在 GitHub 上阅读,这里就不展示了。

  2. vue 项目的 package.json 写入配置,然后安装依赖(建议删除 node_modules 后再安装):

    "dependencies": {
        "monitor-sdk": "file:../..",
    },

    这里的路径跟项目的文件结构有关,可以查看上文的结构图。

  3. vue 项目的入口文件除了容器的挂载等,还要加入如下配置:

    import { init, errorCatch, } from "monitor-sdk";
    
    init({
        // appId,  // 系统id
        cookie: 'foursheep', // 用户id
        // userId: window.localStorage.getItem('session_id') || 'foursheep', // 用户id
        reportUrl: 'http://localhost:8080/report', // 后端 url
        // autoTracker, // 自动埋点
        delay: 0, // 延迟和合并上报的功能
        hashPage: true, // 是否是 hash 路由
        errorReport: true, // 是否开启错误监控
        performanceReport: true // 是否开启性能监控
    })

    从 monitor-sdk 中能导入的 API 就是第一步在 SDK 的入口文件中暴露出来的函数。

当使用暴露出的这些函数后,就会执行 vue 项目的 node_modules 下的 monitor-sdk ,也就是我们封装的监控函数。


文章作者: hcyety
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hcyety !
评论
  目录