写点什么

分享一个 Chrome 控制台数据获取的例子

  • 2022 年 8 月 02 日
  • 本文字数:4232 字

    阅读完需:约 14 分钟

分享一个Chrome控制台数据获取的例子

问题背景

我们公司有开发一个用户访问监测数据采集的sdk,前端应用使用这个SDK呢,就会把用户操作的行为,浏览的页面,访问的请求,加载的时长等操作的数据会采集并上报给服务端。

同时支持应用性能监测(apm 链路追踪)的关联,例如都是非常知名的datadog ddtrace,zipkin,skywalkingotel,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.comxhr 调用的请求头信息都获取到,从请求头信息中把x-datadog-trace-id的值提取出来,写入到文件中(xhr.log)

第二步:

把所有的用户访问监测上报的数据获取到,然后通过正则把trace_id=7866483901034644545这个值7866483901034644545提取出来写入到一个文件中(rum.log)。

最后diff比较下,求xhr.logrum.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.Filevar 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.Connvar 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 -diff5669634225797671685
复制代码


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/

发布于: 刚刚阅读数: 5
用户头像

还未添加个人签名 2018.07.06 加入

还未添加个人简介

评论

发布
暂无评论
分享一个Chrome控制台数据获取的例子_Go_睡着了去做梦_InfoQ写作社区