问题背景
我们公司有开发一个用户访问监测数据采集的sdk
,前端应用使用这个SDK
呢,就会把用户操作的行为,浏览的页面,访问的请求,加载的时长等操作的数据会采集并上报给服务端。
同时支持应用性能监测(apm
链路追踪)的关联,例如都是非常知名的datadog ddtrace
,zipkin
,skywalking
otel
,jaeger
如下图:
左侧大红框住的是APM
链路火焰图(应用性能监测后端调用情况),右侧上小框住的是相关view
(与之关联的前端操作)
问题现象
领导碰到一个问题,部分APM火焰图没有与之关联的相关view
这个难道是用户访问监测数据采集的sdk
有bug吗?,按道理这个数据与应用性能监测数据是一样上报的,难道是没有采集吗?
我暂时认为上面的是一种现象,至于是不是问题,还不能过早下定论。
得先理解技术的实现,看看是否会存在这样的现象。
前端项目中的配置
<scriptsrc="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
相关的链路追踪请求头数据
操作流程
像如下的,我 在前端做一些操作:
添加仪表盘
添加仪表板的请求信息:
可以看到我在前端添加数据,调用API/api/v1/board/add
时,请求头有以下几项数据x-datadog
开头的请求头,这些数据通过trace_id
与前端关联起来
前端用户访问监测SDK
采集上报的数据
去应用性能监测通过trace_id把数据查询出来
我观测后,发现前端做完操作,不是立即马上发送,而是一部分数据合在一起发送,如果任何数据都立即发送:会有大量的发小数据包的请求,这个是比较影响性能的。
如果我做完各种操作,立即关闭浏览器,是很有可能存在,就是应用性能监测有数据,而没有与之关联的用户访问监测数据。当然,这不能说用户访问监测采集就是不存在问题的,说不定真的就是这个采集数据的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
packageprocerimport ( "bufio""fmt""io""os""strings") funcDiff_trace_id() { rum, err :=os.OpenFile("rum_trace_id.log", os.O_RDONLY, 0644) iferr!=nil { fmt.Println("rum_trace_id.log file read error") os.Exit(1) } rum_list :=read(rum) deferrum.Close() xhr, err :=os.OpenFile("xhr_trace_id.log", os.O_RDONLY, 0644) iferr!=nil { fmt.Println("xhr_trace_id.log file read error") os.Exit(1) } xhr_list :=read(xhr) deferxhr.Close() trace_ids :=DiffArray(xhr_list, rum_list) for_, item :=rangetrace_ids { fmt.Println(strings.Trim(item, "\n")) } } funcread(f*os.File) []string { list := []string{} bf :=bufio.NewReader(f) for { line, err :=bf.ReadSlice('\n') iferr!=nil&&err==io.EOF { returnlist } list=append(list, string(line)) } returnlist} funcDiffArray(a []string, b []string) []string { vardiffArray []stringtemp :=map[string]struct{}{} for_, val :=rangeb { if_, ok :=temp[val]; !ok { temp[val] =struct{}{} } } for_, val :=rangea { if_, ok :=temp[val]; !ok { diffArray=append(diffArray, val) } } returndiffArray}
req.go
packageprocerimport ( "encoding/json""os""regexp""strings""sync""github.com/mafredri/cdp/protocol/network") varrum*os.Filevarxhr*os.Filefuncinit() { rum_trace_id, err :=os.OpenFile("rum_trace_id.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) rum=rum_trace_idiferr!=nil { panic(err) } xhr, err=os.OpenFile("xhr_trace_id.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) iferr!=nil { panic(err) } } funcHandle(reqnetwork.RequestWillBeSentReply, wgsync.WaitGroup) { deferwg.Done() head :=make(map[string]string) ifstrings.Contains(req.Request.URL, "/v1/write/rum?precision") { reg :=regexp.MustCompile(`,?trace_id=(\d+),`) list :=reg.FindAllStringSubmatch(*req.Request.PostData, -1) for_, item :=rangelist { iflen(item[len(item)-1]) >0 { rum.WriteString(item[len(item)-1] +"\n") } } return } ifreq.Request.Method!="OPTIONS"&&req.Type!="Font"&&req.Type!="Stylesheet"&&req.Type!="Script"&&req.Type!="Image" { json.Unmarshal(req.Request.Headers, &head) iflen(head["x-datadog-trace-id"]) >0 { xhr.WriteString(head["x-datadog-trace-id"] +"\n") } return } }
cdpdemo.go
packagemainimport ( "cdpdemo/procer""context""flag""fmt""os""strings""sync""time""github.com/mafredri/cdp""github.com/mafredri/cdp/devtool""github.com/mafredri/cdp/rpcc") varaddressmap[string]*rpcc.Connvarwgsync.WaitGroupfuncmain() { 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() } funcrun(url, devtool_addressstring, 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) gofunc() { deferwg.Done() for { time.Sleep(time.Millisecond*650) tar, err :=devt.List(ctx) iferr!=nil { return } for_, t :=rangetar { ifstrings.HasPrefix(t.URL, url) { rpconn, err :=rpcc.Dial(t.WebSocketDebuggerURL) iferr!=nil { fmt.Println(err) continue } if_, ok :=address[t.WebSocketDebuggerURL]; !ok { address[t.WebSocketDebuggerURL] =rpconnwg.Add(1) goaction(rpconn, ctx, wg) } } } } }() returnnil} funcaction(rpconn*rpcc.Conn, ctxcontext.Context, wg*sync.WaitGroup) { deferwg.Done() cdpclient :=cdp.NewClient(rpconn) cdpclient.Network.Enable(ctx, nil) net, err :=cdpclient.Network.RequestWillBeSent(ctx) iferr!=nil { return } defernet.Close() for { reqdata, err :=net.Recv() iferr!=nil { return } wg.Add(1) goprocer.Handle(*reqdata, *wg) } }
我在页面各种操作下来,有产生500条左右的请求(各页面乱点,操作5分钟左右),然后关闭了浏览器,最后发现1个 trace_id
存在差异
$ go run cdpdemo.go -diff5669634225797671685
去查找下这个差异的trace_id,是在中途出现的,那与关闭浏览器就没啥关系了
根据数据得出结论:是存在有应用性能监测数据,而没有用户访问监测的问题。用户访问监测 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/