问题背景
我们公司有开发一个用户访问监测数据采集的sdk
,前端应用使用这个SDK
呢,就会把用户操作的行为,浏览的页面,访问的请求,加载的时长等操作的数据会采集并上报给服务端。
同时支持应用性能监测(apm
链路追踪)的关联,例如都是非常知名的datadog ddtrace
,zipkin
,skywalking
otel
,jaeger
如下图:
左侧大红框住的是APM
链路火焰图(应用性能监测后端调用情况),右侧上小框住的是相关view
(与之关联的前端操作)
image
image
image
问题现象
领导碰到一个问题,部分 APM 火焰图没有与之关联的相关view
image
这个难道是用户访问监测数据采集的sdk
有 bug 吗?,按道理这个数据与应用性能监测数据是一样上报的,难道是没有采集吗?
我暂时认为上面的是一种现象,至于是不是问题,还不能过早下定论。
得先理解技术的实现,看看是否会存在这样的现象。
前端项目中的配置
<script src="https://static.guance.com/browser-sdk/v2/dataflux-rum.js" type="text/javascript"></script>
<script>
window.DATAFLUX_RUM &&
window.DATAFLUX_RUM.init({
applicationId: 'appid_e0da4faf71844ce68ff434db143eccc6',
datakitOrigin: 'https://aliyun-df-rum-dk.guance.com', // 协议(包括://),域名(或IP地址)[和端口号]
env: 'production',
version: '1.0.0',
trackInteractions: true,
traceType: 'ddtrace', // 非必填,默认为ddtrace,目前支持 ddtrace、zipkin、skywalking_v3、jaeger、zipkin_single_header、w3c_traceparent 6种类型
allowedTracingOrigins: ['https://console-api.guance.com', 'https://.*.my-api-domain.com/'], // 非必填,允许注入trace采集器所需header头部的所有请求列表。可以是请求的origin,也可以是是正则
})
</script>
复制代码
我们这里需要关注的是traceType
,allowedTracingOrigins
配置内容,这里的意思是,凡是请求https://console-api.guance.com (请求类型 XHR),就会携带一些ddtrace
相关的链路追踪请求头数据
操作流程
像如下的,我 在前端做一些操作:
添加仪表盘
image
添加仪表板的请求信息:
可以看到我在前端添加数据,调用 API/api/v1/board/add
时,请求头有以下几项数据x-datadog
开头的请求头,这些数据通过trace_id
与前端关联起来
image
前端用户访问监测SDK
采集上报的数据
image
去应用性能监测通过 trace_id 把数据查询出来
image
image
image
我观测后,发现前端做完操作,不是立即马上发送,而是一部分数据合在一起发送,如果任何数据都立即发送:会有大量的发小数据包的请求,这个是比较影响性能的。
如果我做完各种操作,立即关闭浏览器,是很有可能存在,就是应用性能监测有数据,而没有与之关联的用户访问监测数据。当然,这不能说用户访问监测采集就是不存在问题的,说不定真的就是这个采集数据的 SDK 么有发送呢,这个也说不定,所以我们需要有测试手段来测试了。
测试思路
我的思路比较简单:
第一步:
把所有的请求,就是前面配置的https://console-api.guance.com
xhr 调用的请求头信息都获取到,从请求头信息中把x-datadog-trace-id
的值提取出来,写入到文件中(xhr.log
)
第二步:
把所有的用户访问监测上报的数据获取到,然后通过正则把trace_id=7866483901034644545
这个值7866483901034644545
提取出来写入到一个文件中(rum.log
)。
最后diff
比较下,求xhr.log
与 rum.log
的差集
至于用什么方式获取到请求头及请求内容呢?
可以通过Chrome devtools protocol
简称cdp
代码分享
目录结构
$ tree
.
├── cdpdemo
├── cdpdemo.go
├── go.mod
├── go.sum
├── page.pdf
├── procer
│ ├── diff.go
│ └── req.go
├── rum_trace_id.log
└── xhr_trace_id.log
复制代码
diff.go
package procer
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
func Diff_trace_id() {
rum, err := os.OpenFile("rum_trace_id.log", os.O_RDONLY, 0644)
if err != nil {
fmt.Println("rum_trace_id.log file read error")
os.Exit(1)
}
rum_list := read(rum)
defer rum.Close()
xhr, err := os.OpenFile("xhr_trace_id.log", os.O_RDONLY, 0644)
if err != nil {
fmt.Println("xhr_trace_id.log file read error")
os.Exit(1)
}
xhr_list := read(xhr)
defer xhr.Close()
trace_ids := DiffArray(xhr_list, rum_list)
for _, item := range trace_ids {
fmt.Println(strings.Trim(item, "\n"))
}
}
func read(f *os.File) []string {
list := []string{}
bf := bufio.NewReader(f)
for {
line, err := bf.ReadSlice('\n')
if err != nil && err == io.EOF {
return list
}
list = append(list, string(line))
}
return list
}
func DiffArray(a []string, b []string) []string {
var diffArray []string
temp := map[string]struct{}{}
for _, val := range b {
if _, ok := temp[val]; !ok {
temp[val] = struct{}{}
}
}
for _, val := range a {
if _, ok := temp[val]; !ok {
diffArray = append(diffArray, val)
}
}
return diffArray
}
复制代码
req.go
package procer
import (
"encoding/json"
"os"
"regexp"
"strings"
"sync"
"github.com/mafredri/cdp/protocol/network"
)
var rum *os.File
var xhr *os.File
func init() {
rum_trace_id, err := os.OpenFile("rum_trace_id.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
rum = rum_trace_id
if err != nil {
panic(err)
}
xhr, err = os.OpenFile("xhr_trace_id.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
}
func Handle(req network.RequestWillBeSentReply, wg sync.WaitGroup) {
defer wg.Done()
head := make(map[string]string)
if strings.Contains(req.Request.URL, "/v1/write/rum?precision") {
reg := regexp.MustCompile(`,?trace_id=(\d+),`)
list := reg.FindAllStringSubmatch(*req.Request.PostData, -1)
for _, item := range list {
if len(item[len(item)-1]) > 0 {
rum.WriteString(item[len(item)-1] + "\n")
}
}
return
}
if req.Request.Method != "OPTIONS" && req.Type != "Font" && req.Type != "Stylesheet" && req.Type != "Script" && req.Type != "Image" {
json.Unmarshal(req.Request.Headers, &head)
if len(head["x-datadog-trace-id"]) > 0 {
xhr.WriteString(head["x-datadog-trace-id"] + "\n")
}
return
}
}
复制代码
cdpdemo.go
package main
import (
"cdpdemo/procer"
"context"
"flag"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/devtool"
"github.com/mafredri/cdp/rpcc"
)
var address map[string]*rpcc.Conn
var wg sync.WaitGroup
func main() {
targurl := flag.String("url", "https://console.guance.com/", "tagurl")
devtool_address := flag.String("devtool", "http://127.0.0.1:9222", "devtools address")
diff := flag.Bool("diff", false, "diff trace_id")
flag.Parse()
if *diff {
procer.Diff_trace_id()
os.Exit(0)
}
run(*targurl, *devtool_address, &wg)
wg.Wait()
}
func run(url, devtool_address string, wg *sync.WaitGroup) error {
address = make(map[string]*rpcc.Conn)
ctx := context.Background()
// Use the DevTools HTTP/JSON API to manage targets (e.g. pages, webworkers).
devt := devtool.New(devtool_address)
wg.Add(1)
go func() {
defer wg.Done()
for {
time.Sleep(time.Millisecond * 650)
tar, err := devt.List(ctx)
if err != nil {
return
}
for _, t := range tar {
if strings.HasPrefix(t.URL, url) {
rpconn, err := rpcc.Dial(t.WebSocketDebuggerURL)
if err != nil {
fmt.Println(err)
continue
}
if _, ok := address[t.WebSocketDebuggerURL]; !ok {
address[t.WebSocketDebuggerURL] = rpconn
wg.Add(1)
go action(rpconn, ctx, wg)
}
}
}
}
}()
return nil
}
func action(rpconn *rpcc.Conn, ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
cdpclient := cdp.NewClient(rpconn)
cdpclient.Network.Enable(ctx, nil)
net, err := cdpclient.Network.RequestWillBeSent(ctx)
if err != nil {
return
}
defer net.Close()
for {
reqdata, err := net.Recv()
if err != nil {
return
}
wg.Add(1)
go procer.Handle(*reqdata, *wg)
}
}
复制代码
我在页面各种操作下来,有产生 500 条左右的请求(各页面乱点,操作 5 分钟左右),然后关闭了浏览器,最后发现 1 个 trace_id
存在差异
$ go run cdpdemo.go -diff
5669634225797671685
复制代码
image.png
image.png
去查找下这个差异的 trace_id,是在中途出现的,那与关闭浏览器就没啥关系了
image.png
根据数据得出结论:是存在有应用性能监测数据,而没有用户访问监测的问题。用户访问监测 SDK
是存在漏报的现象,需要找开发核实下。
参考连接
https://github.com/ChromeDevTools/awesome-chrome-devtools#chrome-devtools-protocol 包含各语言实现的第三方库
https://github.com/mafredri/cdp go 的第三方库,这个是我使用的
https://chromedevtools.github.io/devtools-protocol/tot/Network/
评论