Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何优雅地定位外网问题——动手搭建用户行为轨迹追踪系统

如何优雅地定位外网问题——动手搭建用户行为轨迹追踪系统

原创
作者头像
elson
修改于 2019-03-04 09:26:53
修改于 2019-03-04 09:26:53
2K2
举报
文章被收录于专栏:Elson's webElson's web

现状分析

在定位外网问题时,最怕的是遇到无法复现或者是偶现的问题,我们无法在用户的设备上通过抓包、打断点或日志来分析问题,只能靠仅有的页面截图和用户的片面描述作为线索。此时,也只能结合“猜想法”和“排除法”进行分析定位,排查了半天也很有可能没有结果,最后只能回复“可能是缓存或者app的原因,请清下缓存或者重新安装app试试”。

导致我们定位外网问题时效率低下,主要还是因为缺乏定位线索;其次由于用户并不了解技术层面的前因后果,他们可能会忽略掉一些关键信息,或者提供了带有误导性的线索。

常见的外网问题成因

从笔者实际上所遇到的外网问题进行归类,主要有以下成因:

  1. 后台数据返回异常,或部分数据为空;
  2. 针对边界情况,页面未做相对应的容错措施,导致页面报错;
  3. 用户的网络环境、APP版本问题;
  4. 通过上一级入口进入页面时,漏传部分参数;
  5. 与用户特定的操作步骤相关所引发。

针对页面JS报错,我们已有脚本异常上报监控机制,业界也不乏相关的优秀开源产品,如sentry。但往往很多情况下的用户反馈以及外网异常并不是脚本异常引起的,此时无法触发异常上报。因此针对这部分场景,我们需要有另一套机制进行上报监控,辅助我们定位分析。

用户的行为轨迹的重要性

从上面的问题成因可以得出,如果我们能采集到并结合以下几方面数据,那外网异常的定位自然会事半功倍:

  • 页面的运行环境
  • 页面所加载的数据
  • 页面JS报错信息
  • 用户的操作日志(时间线)

我们可以通过时间戳将以上数据串联起来,形成时间线。这样一来,页面的运行环境、页面中每个动作相关的数据、动作之间先后关系就会一目了然,就像一部案发现场的录像。因此这里强调“轨迹”的重要性,能够把散乱的数据串联起来,这对我们分析定位问题非常有帮助。

基于上面的分析结论,我们搭建了一套用户行为轨迹追踪系统,大致工作流程为:在页面中加载JS SDK用于数据记录和上报,服务器接收并处理数据,再以接口的方式提供数据给内部查询系统,支持通过用户UIN以及页面地址进行查询。

下面我们从报什么、怎么报、服务器如何处理数据、数据怎样展示四方面具体谈一下整体的设计思路。

设计思路

报什么:确定上报内容及协议

根据上面的分析,我们已经初步得出了需要上报的数据内容。

上报的内容最终需要落地到查询系统中,因此首先需要确定怎样查询。我们将用户在某页面的单次访问作为基本查询单位,假设某用户访问了3次A页面,那么在查询平台中就可以查出3条记录,每条记录可以包含多条不同类型的子记录,它们共用“基础信息”。大致的数据结构如下:

代码语言:txt
AI代码解释
复制
const log = {
    baseInfo: {},
    childLogs: [{...}, {...}, ...]
};
基础信息

baseInfo中记录的是页面的运行环境,可以称为“基础信息”,具体包括以下字段:

字段名

描述

可选参数

FtraceId

某次页面访问的唯一标识(自动生成)

Fua

navigator.userAgent

FclientType

客户端类型

0:未知 1:qqmusic 2:weixin 3:mqq

Fos

系统

0:未知 1:ios 2:android

Furl

页面地址 navigator.userAgent

Frefer

页面上级入口 document.referrer

FloginType

帐号类型

0:wx 1:qq

Fuin

用户帐号

childLogs中保存所有子记录,以下是子记录的公用字段以及三种不同类型。

子记录公共字段

每条子记录需要记录时间戳、标识上报类型,因此需要定义以下的公共字段:

字段名

描述

可选参数/格式

备注

Flogtype

上报类型

0: ajax通信 1:用户操作 2:报错异常

FtimeStamp

时间戳

串联不同类型的上报记录,形成轨迹

Forder

数字顺序

Number

当前记录在整条轨迹中的自增序号

Forder的作用在于当两条记录的 FtimeStamp 值相同时,作为辅助的排序依据。

子记录类型1:ajax通信

记录页面中所有ajax通信的数据,方便排查异常是否与后台数据有关。

字段名

描述

可选参数

FajaxSendTime

ajax请求发起时间点

FajaxReceiveTime

ajax数据接收到时间点

FajaxMethod

ajax请求类型

0:get 1:post

FajaxParam

ajax请求参数

FajaxUrl

ajax请求链接

FajaxReceiveData

ajax请求到的数据

FajaxHttpCode

http返回码(200, 404)

FajaxStateCode

后台返回的业务相关code码

子记录类型2:用户操作行为

记录打点数据以及用户点击操作的DOM上的数据

字段名

描述

可选参数/格式

FtraceContent

自定义上报内容

String

FdomPath

操作目标DOM的xpath

Fattr

目标DOM的所有data-attr属性及其值

{att1: '123', att2: '234'}

子记录类型3:报错异常

记录JS报错信息以及我们手动抛出的异常信息

字段名

描述

可选参数/格式

备注

FerrorType

错误类型

0:原生错误 1:手动抛出的异常

FerrorStack

错误堆栈

仅原生错误报

FerrorFilename

出错文件

FerrorLineNo

出错行

FerrorColNo

出错列位置

FerrorMessage

错误描述

原生错误的errmsg或者开发自定义

怎么报:SDK的数据采集及上报策略

上述的数据需要通过页面加载SDK进行采集,那么怎样采集,如何上报?

数据采集方式

从业务场景以及常见的外网问题考虑,我们只关注带有登录态的场景。对于未登录或获取不到登录态的场景,SDK不做任何数据采集和上报。

( 1 ) 基础信息

FtraceId可以直接搜 uuid 的生成算法,用户每进入页面时自动生成一个,后续采集的子记录共用此 ID。

其他字段则可以从 cookie 或者原生 API 中获取,这里不再赘述。

( 2 ) ajax 通信数据

这里用到了一个开源组件 Ajax-hook ,源码很简练,GZIP 后只有 639 字节。主要原理是通过代理 XMLHttpRequest 以及相关实例属性和方法,提供各个阶段的钩子函数。

代码语言:txt
AI代码解释
复制
hookAjax({
    open: this.handleOpen,
    onreadystatechange: this.handleStage
});

一次 ajax 通信包含 opensendreadyStateChange 等阶段,因此需要在不同阶段的钩子函数中采集从请求发起到接收到请求响应的各方面数据。

具体来说

  1. open 中可以采集:请求发起时间点、请求方法、请求参数等。需要注意过滤掉无用的请求,如数据采集后的上报请求。
  2. send 中主要用于采集 POST 请求的请求参数。
代码语言:txt
AI代码解释
复制
handleOpen(arg, xhr) {
        const urlPath = arg[1] && arg[1].split('?');
        xhr.urlPath = urlPath[0];
        
        // 过滤掉上报请求
        if (/stat\.y\.qq\.com/.test(urlPath[0])) {
            return;
        }

        curAjaxFields = $.extend({}, ajaxFields, {
            FtimeStamp: getNowDate(),
            FajaxSendTime: getNowDate(),
            FajaxMethod: arg[0] ? methodMap[arg[0].toUpperCase()] : '',
            FajaxUrl: urlPath[0],
            FajaxParam: urlPath[1],
            Forder: logger.order++
        });
        
        xhr.curAjaxFields = curAjaxFields;

        const _oriSend = xhr.send.bind(xhr);
        xhr.send = function(body) {
            // POST请求 获取请求体中的参数
            if (body) {
                curAjaxFields.FajaxParam = body;
            }
            _oriSend && _oriSend(body);
        };
    }
  1. readyStateChange 中,当 xhr.readyState 为 2(HEADERS_RECEIVED) 或 4(DONE) 时,分别采集 FajaxReceiveTime 和 响应数据相关数据。这里需要注意的,为了把前期从 opensend 中采集到的数据传递下来,我们将数据对象挂载在当前 xhr 对象上: xhr.curAjaxFields = curAjaxFields;
代码语言:txt
AI代码解释
复制
handleStage({ xhr }) {
        // 过滤掉上报请求
        if (/stat\.y\.qq\.com/.test(xhr.urlPath)) {
            return;
        }
        switch (+xhr.readyState) {
            case 2: // HEADERS_RECEIVED
                $.extend(xhr.curAjaxFields, {
                    FajaxReceiveTime: getNowDate(),
                    FajaxHttpCode: xhr.status
                });
                break;

            case 4: // DONE
                const xhrResponse = xhr.response || xhr.responseText;
                let jsonRes;
                
                try {
                    // 如果回包不是json格式的话会报错
                    jsonRes = xhrResponse ? JSON.parse(xhrResponse) : '';
                    ...
                } catch (e) {
                    console.error(e);
                }
                
                $.extend(xhr.curAjaxFields, {
                    FajaxReceiveData: xhrResponse,
                    FajaxStateCode: jsonRes ? getStateCode(jsonRes).join(',') : ''
                });
                
                break;
        }
 }

( 3 ) 用户操作行为

通过事件代理,在 document 上监听指定类 .js_qm_tracer 的事件回调。在回调中通过event.path 取到当前 dom 的路径;通过 event.currentTarget.attributes 取到当前 dom 上的所有属性。

同时还提供 API 实现自行上报 action.report(data)

代码语言:txt
AI代码解释
复制
$(document).on('click', '.js_qm_trace', e => {
    const target = e.currentTarget;
    // 时间戳
    let FtimeStamp = getNowDate();

    // Dom的xpath
    let FdomPath = _getDomPath(e.path);

    // dom的所有data-attr属性以及值
    let Fattr,
        FtraceContent = null;
    if (target.hasAttributes()) {
        let processedData = _processAttrMap(target.attributes);
        Fattr = processedData.Fattr;
        FtraceContent = processedData.FtraceContent;
    }
    ......
});
上报策略

上面的数据,如果我们记录一条就上报一条,这无疑是给自己制造DDOS攻击。此外,我们的初衷在于帮助排查外网问题,因此在我们需要用的时候再报上来就行了。所以需要引入本地缓存和用户白名单机制,采集完先在本地缓存起来,需要的时候再根据用户白名单“捞取”。

本地缓存机制我们选用的是 IndexedDB,它容量大( 500M ),异步读写的特性保证其不会对页面渲染产生阻塞,此外还支持建立自定义索引,易于检索,更适合管理采集到的数据。

用户白名单机制则是通过一个后台服务,SDK初始化后都会先查询当前用户和页面URL是否均在白名单中,是的话则将之前缓存的数据进行上报,而之后的用户行为操作也会直接上报,不再先缓存。

但如果遇到JS错误报错,属于紧急情况,这时则不再遵循“缓存优先”,而是直接上报错误信息以及当前采集到的其他数据。

上报策略流程图:

白名单机制流程图:

获取到白名单用户的数据需要用户再次访问页面,一方面从性能和开发成本考虑,另一方面反馈外网问题的用户很大概率是会再次访问当前页面的。只需要再次进入页面,无需额外操作,这样对用户来说也没有沉重的操作成本和沟通成本,简单易操作。

数据处理:服务器对数据的处理策略

( 1 ) 首先,数据上报请求经过 nginx 服务器后,会生成 access.log。

代码语言:txt
AI代码解释
复制
http {
    log_format trace '$request_body';
    
    server {
        location /trace/ {
           client_body_buffer_size 1000m;
           client_max_body_size 1000m;
           proxy_pass http://127.0.0.1:6699/env;
           access_log /data/qmtrace/log/access.log trace;
       }
    }
    
    server {
        listen 6699;
        location /env/ {
            client_max_body_size 1000m;
            alias /data/qmtrace/;
       }
    }
}

使用 nginx 日志进行记录,主要是因为 nginx 优异的性能,能抗住高并发;此外其接入和维护成本也较低。

这里在处理 POST 请求的日志时,遇到一个坑。如果不经过 proxy_pass 转发一次的话,nginx 无法对 POST 请求产生日志记录。

此外需要注意的是缓冲区的大小, client_body_buffer_size 默认只有 8K 或 16K,如果实际请求体大小超过了它,那就会被忽略,无法产生日志记录。

( 2 ) 通过 crontab 每五分钟定期处理一次 access.log

access.log 移动到相应的以年月日小时命名的目录下,生成 access_${minute}.log

移走 access.log 之后,此时需要执行以下命令,发送通知给 nginx,收到通知后会重新生成新的 access.log

代码语言:txt
AI代码解释
复制
kill -USR1 `cat ${nginx_pid}`

最后用node脚本,对 access_${minute}.log 进行解析处理后入库。

数据展示:搭建查询平台

查询平台
查询平台

采集到的数据,在内部查询平台通过用户 UIN 进行检索,同时支持输入特定的页面 URL,进一步聚焦检索结果。

在之前我们提到,将用户在某页面的单次访问作为基本查询单位,假设某用户访问了3次A页面,那么在左侧就会检索出3条记录(每条记录都有唯一标识 FtraceId )。

为了查询平台的性能考虑,每次查询只会返回左侧的记录列表以及第一条记录的详细信息。点击其他记录再根据 FtraceId 进行异步查询。

右侧展示的是某条记录的详细信息,通过时间线的形式将用户在某次页面访问期间的行为轨迹直观地展示出来。通过客观且直观的用户轨迹数据,我们就可以更高效更有针对性地分析定位外网问题。

总结

我们通过报什么(上报内容及协议)、怎么报(SDK采集及上报策略)、数据如何处理、数据怎样展示,四个方面介绍了如何搭建用户行为轨迹追踪系统。目前只是个初级版本,有很多地方需要继续完善和改进。有了追踪用户轨迹数据,能够从很大程度上有效灵活地应对用户反馈和外网异常,从而也很好地提升了我们的工作效率。

参考

  1. 前端异常监控解决方案研究
  2. 监控平台前端SDK开发实践
  3. 浏览器数据库 IndexedDB 入门教程
  4. Ajax-hook 原理解析

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
2 条评论
热度
最新
不错好文章建议你可以去看看smh12345.com里面写的
不错好文章建议你可以去看看smh12345.com里面写的
11点赞举报
额... 你发这个网址是几个意思。。
额... 你发这个网址是几个意思。。
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
从0到1搭建前端监控平台,面试必备的亮点项目
常常会苦恼,平常做的项目很普通,没啥亮点;面试中也经常会被问到:做过哪些亮点项目吗?
前端老道
2023/02/27
3.7K0
从0到1搭建前端监控平台,面试必备的亮点项目
搭建前端监控,采集用户行为的 N 种姿势
上一篇我们详细介绍了前端如何采集异常数据。采集异常数据是为了随时监测线上项目的运行情况,发现问题及时修复。
杨成功
2022/09/22
1.4K0
一文搞懂得物前端监控
得物的服务端监控是比较全面和有效的,除了上报原始日志数据,还通过数据分析制定线上告警机制,调用链路分析,而针对前端项目这一块,还是不够全面的。对前端线上问题感应不及时,靠人肉发现,没有告警机制等问题,所以就有个前端监控这个项目。前端监控也确实很有必要,我们需要对线上的页面有个全面的把控,而至于怎么做监控,做数据上报,以及数据分析,如何针对监控数据分析出有用的核心链路的告警等也能有个全面的认识。本文主要是介绍得物针对监控做了哪些事情以及对前端底层监控手段做个总结。
得物技术
2023/12/13
7870
一文搞懂得物前端监控
项目实战-埋点系统初探
最近杂七杂八的事情比较多,难得抽出时间来弥补一下之前的系列,欠大家的埋点系列现在开始走起来
Cookieboty
2020/10/23
2.2K0
项目实战-埋点系统初探
目前为止整理最全的前端监控体系搭建篇(长文预警)
PV(page view) 是页面浏览量,UV(Unique visitor)用户访问量。PV 只要访问一次页面就算一次,UV 同一天内多次访问只算一次。
前端达人
2022/04/18
12.5K1
目前为止整理最全的前端监控体系搭建篇(长文预警)
自己动手打造前端性能监控系统
我们从三个各方面,前端上报,数据收集和入库,数据展示来介绍了如何打造一个测速系统。
QQ音乐前端团队
2018/08/28
3.7K8
自己动手打造前端性能监控系统
web前端监控的三个方面探讨
以 init 为程序的入口,代码中所有同步执行出现的错误都会被捕获,这种方式也可以很好的避免程序刚跑起来就挂。
smy
2018/08/01
1.2K0
web前端监控的三个方面探讨
沉淀了3年的自研前端错误监控系统,打通你的脉络
这篇文章是我的好朋友广胤所写,里面记录了我们2018年探索的前端监控体系的历程,由于在建设完后的我离职了,后续也没有继续能和广胤一起更进一步的探索,还是有一些些遗憾。还记得我第一次进入「兑吧」的时候,我就在简历里描述了错误监控之类的项目,其实当时我并没有在一个公司进行过实践,这大概是之前在网易的时候,闲来没事,进行的自我探索。然后进入「兑吧」后,没想到当时公司正好缺少这一块的基建,于是 TL 就让我和广胤负责了这块项目,也是这次经历让我从实习阶段就正式踏入了前端基础建设的道路,还是非常感谢这一次的机会,让我从单一的业务开发人员,转化到了结构型开发人员。记得在开发的项目的那一个月中,除了吃饭,或者和广胤讨论项目的进度问题,近乎一种忘我的开发状态。
秋风的笔记
2021/07/30
1.1K0
HTTP跨域详解和解决方式
那么究竟什么是跨域,跨域又是怎么产生的,以及跨域请求的问题需要怎么解决。我们一起来了解一下这些知识。
仙士可
2019/12/19
4.9K0
HTTP跨域详解和解决方式
「深入浅出」前端开发中常用的几种跨域解决方案
看完本文可以系统地掌握,不同种跨域解决方案间的巧妙,以及它们的用法、原理、局限性和适用的场景
用户6835371
2021/06/01
9840
「深入浅出」前端开发中常用的几种跨域解决方案
搞点事情,使用node搭建反向代理
骑猪耍太极
2017/04/28
2.7K0
搞点事情,使用node搭建反向代理
前端 JavaScript 错误分析实践
在平日的工作中前端 badjs 是一个比较常见的问题, badjs 除了我们自身业务 js 脚本里比较明显的报错外还有依赖其他资源的一些报错,对于自身业务 js 里出现的错误很容易进行定位并修复,但对于依赖资源的错误即常见的 script error (外部 js、接口错误)定位就没那么容易了。
WecTeam
2019/12/16
1K0
前端 JavaScript 错误分析实践
高级前端面试题汇总_2023-02-27
这里需要注意的是在async1中await后面的Promise是没有返回值的,也就是它的状态始终是pending状态,所以在await之后的内容是不会执行的,包括async1后面的 .then。
用户10377405
2023/02/27
1.8K0
前端监控 SDK 的一些技术要点原理分析
本文要讲的就是其中的第一个环节——数据采集与上报。下图是本文要讲述内容的大纲,大家可以先大致了解一下:
谭光志
2022/03/24
2.4K0
前端监控 SDK 的一些技术要点原理分析
瞧瞧别人家的API接口,那叫一个优雅
在实际工作中,我们需要经常跟第三方平台打交道,可能会对接第三方平台API接口,或者提供API接口给第三方平台调用。
苏三说技术
2022/12/19
9400
Web跨域总结
什么是同源 浏览器安全的基石是“同源政策”,所有浏览器都实行这个政策。所谓两个网页“同源”指的两个网页的“协议相同”、“域名相同”、“端口相同”。 浏览器为什么遵循同源政策 同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,将会产生严重的信息安全问题。 不同源的两个网页有哪些限制 各自无法读取对方的Cookie、LocalStorage 和 IndexDB 各自无法操作对方的DOM
jeremyxu
2018/05/10
9040
腾讯医疗健康高级工程师一线分享:鹅厂人都在用的小程序监控“神器”
刘志祥 腾讯医疗健康高级前端开发工程师,腾讯前端监控 Oteam PMC 成员,主要负责小程序监控系统的设计和开发。 微信小程序现状发展 在今年的微信公开课 PRO 上,微信小程序负责人曾鸣披露了2021年小程序的大盘数据。数据显示,2021年微信小程序 DAU(日活) 达到了 4.5亿+ 的规模,小程序开发者超过了 300 万。 随着小程序的高速发展,越来越多的重点业务以小程序的产品形态展示在用户面前。前端作为用户访问业务的直接途径,对用户体验的重要性不言而喻,若出现页面出错、卡顿、崩溃、损坏等页面异
腾讯云可观测平台
2022/08/31
7880
腾讯医疗健康高级工程师一线分享:鹅厂人都在用的小程序监控“神器”
跨域访问知多少
浏览器的同源策略,要同源说起。顾名思义,同源就是源头相同,即两个页面的协议、端口和域名都相同,任何一个不满足,都会导致跨域。
黑洞代码
2021/01/14
1.4K0
从实用角度浅析前端全链路质量监控中台技术方案
无论是纯前端业务还是服务端业务,线上质量的保障都是我们的底线要求,也是我们日常需要花费很多精力关注的环节。
陈煮酒
2022/11/18
6340
从实用角度浅析前端全链路质量监控中台技术方案
AJAX 与跨域通信(二):跨域解决方案
本篇讲解常见的几种跨域方案:JSONP、CORS、图像Ping、document.domain、window.name。
Chor
2019/11/11
1.4K0
AJAX 与跨域通信(二):跨域解决方案
相关推荐
从0到1搭建前端监控平台,面试必备的亮点项目
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档