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点赞举报
额... 你发这个网址是几个意思。。
额... 你发这个网址是几个意思。。
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
文本处理三驾马车之 awk
Awk 是一个强大的文本分析工具,它每次读入一条记录,并把每条记录切分成字段后进行分析。Awk 官方文档是非常好的学习材料,通过man awk查看。
简说基因
2024/02/23
2080
文本处理三驾马车之 awk
生物信息重要的文本处理命令(实例命令及解释)
linux文本处理命令是一类对文件进行操作的命令,通过使用文本处理命令,可以轻松的对文件进行排序,拆分,合并等操作,熟练掌握文本处理命令,在生物信息文本处理中,有十分重要的意义。
生信交流平台
2020/08/31
1.3K0
Linux进阶 03 文本处理三驾马车
⭐重头戏来啦!真的很难很繁琐,每个命令下面又有许多个小参数,套娃!先记录下来以后要多看看~
可乐同学与生信死磕到底
2024/04/18
2691
【Linux】文本处理三剑客:grep、sed 和 awk
在日常的开发、运维、数据分析等工作中,我们经常需要处理大量的文本数据。无论是日志分析、配置文件修改,还是数据提取与格式化,命令行工具 grep、sed 和 awk 都是不可或缺的得力助手。它们被戏称为“文本处理三剑客”,为我们提供了高效且灵活的方式来处理和操作文本数据。
人不走空
2024/12/10
4340
linux awk命令使用详解
Awk是一种文本处理工具,它可以用来从文本文件中提取数据并对其进行处理。Awk命令非常强大,可以将它用于各种文本处理任务,包括数据转换、数据提取、报告生成等。在本文中,我们将深入探讨Awk命令的用法,并提供一些常见的示例。
堕落飞鸟
2023/03/27
2.3K0
Linux的文本处理工具浅谈-awk sed grep
日志样子举例如下: http://youku.com 200 http://youku.com 302 http://youku.com 403 http://youku.com 502 http://baidu.com 302 http://baidu.com 404 现想使用awk命令按域名统计 返回码大于等于400的百分比,假如优酷总共有4行,大于等于400的返回码有两行,那占比就为50%
sunsky
2020/08/20
3.6K0
linux三剑客之awk,linux必学的强大工具!
Linux 文本处理三剑客grep、sed、awk,这三个命令在工作和面试过程中出现的频率非常高,有时候很复杂的需求,一条简单的命令就可以实现,今天就先学习一下最强大的awk。
吾非同
2021/12/21
2.5K0
linux三剑客之awk,linux必学的强大工具!
文本处理小记
在平时的测试过程中,经常会遇到各种文本处理的问题,于是把遇到的常用的文本处理命令和方法进行了总结和整理。
用户5521279
2020/03/23
8700
shell -- AWK&文本处理 浅析
markdown 编辑,来写awk真是麻烦 awk 入门: awk 是格式化文本处理最常用的工具,日常捞数据、切数据最常用的,当然了不用awk 也有其他的工具能解决问题,但是经过检验 awk可以说是最好用的。说实话对于新手来说 awk上手可能会慢一些,但是用习惯了即将溜的飞起。 下面从我工作时常用的一些方式来阐述这个命令(网上也有很多的资料可以对比参考): 假设1.demo 文件内容如下: 1 2 3 4 abc 1 2 3 4 abcdce awk '{print
邹志全
2019/07/31
6620
Galaxy生信云平台|制作临床信息表/三线表/Table 1
临床基线表是在临床研究中用于记录和收集患者初始诊断、治疗前的基本信息以及其他关键变量的表格或数据库。这些基本信息通常被用作研究的起点,用于建立患者的初始状态或基线状态。
简说基因
2023/10/25
8310
Galaxy生信云平台|制作临床信息表/三线表/Table 1
Linux awk命令详细教程
AI摘要:本文详细介绍了Linux下的文本处理工具awk的使用方法,包括其语法、选项参数、内置变量、常用操作(如打印、条件判断、循环等)以及一些实用的示例。awk通过将文本按行读取并以指定的分隔符分割成多个字段,使得对文本的处理变得灵活高效。文章还提供了如何打印指定列、过滤日志、按条件统计、指定多个分隔符、日志切割、匹配指定列和统计文件大小等实际应用示例,以及一个统计nginx日志数据的awk脚本示例。通过这些内容,读者可以全面掌握awk命令的强大功能,有效地应用于日志分析、数据处理等场景。
曈曈too
2024/03/16
4700
awk工具详解
它是专门为文本处理设计的编程语言,也是行处理软件,通常用于扫描、过滤、统计汇总工作数据可以来自标准输入也可以是管道或文件
全栈程序员站长
2022/09/07
3.2K0
awk工具详解
UseGalaxy.cn生信云平台文本文件操作手册
文本文件是生物信息学中应用非常广泛的文本格式,甚至可以说是最重要的文件格式,比如常见的测序下机数据Fastq、参考基因组保存格式Fasta、比对文件SAM,以及突变列表VCF,它们都是文本文件。熟练地进行文本文件的处理,对于生信数据分析来说非常重要。比如为特定程序准备相应的输入文件,或者从结果文件中提取需要的信息。
简说基因
2023/11/11
2910
UseGalaxy.cn生信云平台文本文件操作手册
Linux三剑客之awk入门指引
今天1024 程序猿节,百忙中抽空发篇一直想写好久的文章来凑个热闹,简单教大家如何使用awk这个命令行工具。认识我的人都知道我最早是运维出生,做运维没学会啥太大的本事,有些命令行工具却使得贼溜,awk就是其中之一。后来我转开发后,凭借精通部分命令行工具的使用快速解决过很多小问题,命令行的便捷和高效也曾多次震惊到我们的同事们。
xindoo
2024/08/07
1790
awk - 文本和数据进行处理的编程语言
awk 是一种用于文本和数据处理的编程语言,在数据处理、文本分析等领域应用广泛,以下是关于它的详细介绍:
是山河呀
2025/02/02
2440
linux文本处理三剑客之awk
seq 100 |awk '{sum=sum+$1;print sum}END{print sum}'
黑马金牌编程
2022/04/12
8310
命令行工具:awk文本处理
awk 一个强大的工具,可以同时处理行和列,好多C语言内置函数可以集成进来,非常灵活。基本模式是awk 'BEGIN{print "start"} pattern {commands} END {print "end"} file',其中BEGIN和END可选,就是开始执行真正的循环之前和之后执行的操作。 简介 有几个特殊的变量: NR:number of current row,当前行号; NF:number of fields,总共有多少个字段,默认是按空格分字段的; $0:当前行段内容; $1
用户2183996
2018/06/21
7940
Linux之awk命令详解(二)
上一篇文章我们简单举了几个例子了解了一下awk命令的基本语法,这里,再次贴出来这个命令的基本语法,如下:
AsiaYe
2019/11/06
2.7K1
Linux 文本处理三剑客应用
Linux 系统中文本处理有多种不同的方式,系统为我们提供了三个实用命令,来实现对行列的依次处理功能,grep命令文本过滤工具,cut列提取工具,sed文本编辑工具,以及awk文本报告生成工具,利用这三个工具可以灵活的过滤截取任何系统文本内容。
王 瑞
2022/12/28
1.3K0
awk 简单使用教程
之前我一直使用 Python 来处理 Linux 的一些文本,但是对于一些大文本的简单处理,Python 麻烦而且慢,于是现在慢慢改用awk来处理,很多时候一行命令就能解决,因此非常方便。针对使用是过程的一些心得,写个小小的教程,awk太强大了,需要慢慢长时间的学习,我尽量保持更新这个教程吧。
泽霖
2023/11/28
2830
相关推荐
文本处理三驾马车之 awk
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档