- 可观测性
- 后端
【免费下载链接】highlight
highlight.io: The open source, full-stack monitoring platform. Error monitoring, session replay, logging, distributed tracing, and more.
本篇技术指南围绕 highlight.io 开源全栈可观测平台的核心能力之一——**错误分组(Grouping Errors)**展开,系统讲解错误实例如何被归并到同一错误组(Error Group):先是“错误消息 + 栈帧指纹”的经典匹配规则,再是面向 JSON 结构化错误的 JSONPath 自定义分组表达式,最后结合仓库源码揭示其底层打分与匹配流程。读完本文,你将理解为什么看似不同的报错会被归为一组、如何在项目设置中通过
$.type这类表达式定制分组维度,以及源码层指纹(Fingerprint)与分组查询是如何落地的。
为什么需要错误分组
在生产环境中,同一处缺陷往往会以成千上万条错误实例(Error Object)的形式反复出现——不同用户、不同浏览器、不同时间点触发同一段代码。如果逐条展示,开发者会被海量重复的噪音淹没,无法定位根因。
highlight.io 的解法是引入**错误组(Error Group)**抽象:每当一个错误被抛出并上报,后端会尝试把它匹配到某个已有的错误组;匹配成功,就把这条新错误实例并入该组;匹配失败,则新建一个错误组。这样一来,一个 Bug 对应一个错误组,开发者看到的是“某个错误发生了多少次、影响了多少用户”,而非一屏重复的堆栈。
相关说明可参见仓库文档 grouping-errors.md,其核心规则可概括为:
Highlight groups errors together based on their error message and stack trace. When an error is thrown, Highlight finds the closest matching error and adds the new error instance to it.
错误实例如何匹配到错误组
文档中给出的匹配判定条件如下,任一满足即视为命中:
- 相同的错误消息(error message),或
- 相同的顶部栈帧(top stack frame),且紧接着的 4 个栈帧中有 3 个相同(顺序不限)。
其中“栈帧匹配”的判定为:
- 文件名、函数名、行号、列号全部相同,或
- 若开启了 Source Map,则源代码及其上下文相同。
如果没有与任何现有错误组匹配,系统会为该错误新建一个错误组。
匹配规则背后的设计考量
从匹配条件可以读出两个设计意图:
- 错误消息是分组的第一权重维度。只要消息一致,即使堆栈细节略有出入也会被归并,适合捕获“同一句话在不同位置抛出”的场景。
- 栈帧序列用于兜底。当错误消息因包含动态内容(行号、时间戳、用户输入)而无法精确相等时,栈帧的“结构相似性”就派上用场——顶部帧必须一致,后续 4 帧只需命中 3 帧,且允许乱序。这能容忍编译器优化、内联函数、以及微小的代码位移带来的栈差异。
源码中的“指纹(Fingerprint)”对应物
在 backend/errorgroups/fingerprint.go 中,GetFingerprints把每条结构化栈帧拆解为两类指纹:
StackFrameCode(CODE):由该帧的LinesBefore、LineContent、LinesAfter拼接而成,即文档所说的“源代码及上下文”,只有当 Source Map 开启、能还原源码时才有值;StackFrameMetadata(META):由FileName、FunctionName、LineNumber、ColumnNumber拼接而成,即文档所说的“文件名、函数名、行号、列号”。
可见文档中“栈帧匹配”的两条规则,在实现层面分别对应 CODE 与 META 两类指纹的比对。指纹生成时的Index字段记录了它在栈中的位置——index == 0是顶部帧,index <= 4是紧随其后的候选帧,这正对应“顶部栈帧 + 后 4 帧”的窗口范围。
分组打分:底层 SQL 如何实现“最接近匹配”
文档描述了匹配的判定条件,而仓库 backend/public-graph/graph/resolver.go 中的GetTopErrorGroupMatch(L568)给出了真正的实现:它把候选错误组的得分做加权求和,取最高分作为“最接近的匹配”。
计分逻辑与文档规则一一对应:
| 命中来源 | 得分 | 对应文档规则 |
|---|---|---|
| 错误组事件消息与当前错误事件完全相等 | 100 | “相同的错误消息” |
| 当前项目的 JSONPath 表达式解析值命中已有 JSON 指纹 | (2 ^ 序号) * 1000 | “Grouping Rules”中自定义 JSONPath 分组 |
顶部栈帧(index == 0)的 META 或 CODE 指纹命中 | 10 | “相同的顶部栈帧” |
index 1..4的 META 或 CODE 指纹命中 | 1 | “后 4 帧中相同帧” |
随后通过一个最小阈值完成最终判定(L677-L686):
minScore := 10 + len(restMeta) - 1 if len(restCode) > len(restMeta) { minScore = 10 + len(restCode) - 1 } if result.Sum > minScore { return &result.Id, nil // 匹配成功,并入该组 } else { return nil, nil // 无匹配,新建错误组 }这个阈值正好呼应“顶部帧相同 + 后 4 帧中 3 帧相同”的最低要求:顶帧 10 分,再加上至少 3 个后续帧各 1 分,因此最小命中分为10 + 3 - 1 = 12;minScore的-1修正项是为了规避边界歧义。换言之,只有真正达到文档所述“顶帧一致 + 3/4 帧命中”的相似度,才会被判定为同一错误组。
补充说明:该函数注释中提到某个特定项目因超长堆栈拖慢分组过程而临时关闭了匹配(L608-L614),说明分组 SQL 的性能与堆栈规模相关,属于实现细节而非通用行为。
面向 JSON 错误的自定义分组规则(Grouping Rules)
文档的第二部分聚焦于JSON 形式的错误。如果错误以 JSON 结构上报,默认的“消息精确相等 + 栈帧相似”规则可能过于严格——例如同样类型的错误,只因发生在不同代码行,消息文本不同、栈帧也不同,就无法归并。
文档给出的示例:
{ "type": "StackOverflowError", "user": "alice", "message": "Oh no! You got an error on line 41!!" } { "type": "StackOverflowError", "user": "bob", "message": "Oh no! You got an error on line 50!!" }这两条错误消息不同、抛出行也不同,按默认规则不会被归入同一组。若希望把所有type相同的错误合并到同一错误组,可在项目设置(project settings)中添加 JSONPath 表达式$.type,系统就会基于该字段的值进行分组。
实现层面:JSONPath 的校验与提取
JSONPath 表达式通过editProjectSettings变更(backend/private-graph/graph/schema.resolvers.go),保存前会用jsonpath.New(path)逐一校验,非法表达式会直接报错拒绝提交:
for _, path := range errorJSONPaths { _, err := jsonpath.New(path) if err != nil { return nil, e.Wrap(err, "The JSON path '"+path+"'is not valid") } }校验通过后写入项目模型Project.ErrorJsonPaths(backend/model/model.go)。
而在错误上报侧,backend/public-graph/graph/resolver.go 的handleErrorAndGroup会尝试把错误事件反序列化为 JSON,并对每条已配置的 JSONPath 求值,将结果作为JsonResult类型的新指纹追加到指纹集合:
for _, path := range project.ErrorJsonPaths { value, err := jsonpath.Get(path, errorAsJson) if err == nil { marshalled, err := json.Marshal(value) if err == nil { jsonResult := model.ErrorFingerprint{ ProjectID: projectID, Type: model.Fingerprint.JsonResult, Value: path + "=" + string(marshalled), } fingerprints = append(fingerprints, &jsonResult) } } }注意指纹的Value是path + "=" + 求值结果的形式,因此不同 JSONPath 表达式产生的指纹彼此隔离、不会混同。在打分 SQL 中,JSON 指纹使用(2 ^ ordinality) * 1000的指数级权重(L617-L635),且结果会先反转再参与匹配(L595-L596)——多表达式场景下先配置的表达式权重更高,从而保证多条 JSONPath 规则叠加时判定稳定、可预期。
典型使用场景与配置建议
场景一:按错误类型分组
对文档示例中的StackOverflowError场景,配置:
$.type即可让所有type == "StackOverflowError"的错误汇聚到一个错误组,无论发生在第 41 行还是第 50 行。
场景二:按业务字段分组
若错误对象携带业务上下文,例如:
{ "service": "checkout", "error": "payment declined", "userId": "u-10086" }- 配置
$.service:按微服务维度分组; - 配置
$.error:按错误描述分组; - 配置
$.userId:按用户维度分组(不推荐用于分组,会因用户基数过大而碎片化)。
配置入口
- 网页端:在应用设置(Project Settings)的 Error Monitoring 相关区域填入 JSONPath 表达式列表;
- API 侧:通过 GraphQL 变更
editProjectSettings的errorJSONPaths参数提交(见 backend/private-graph/graph/schema.resolvers.go)。
配置建议
- 表达式越少越好:每条表达式都会成为分组指纹的一部分,过多的分组维度会把错误组切得太碎,不利于聚合分析;
- 优先选择低基数字段:像
type、service、error code这类取值有限、语义稳定的字段是最佳候选; - 避免高基数字段:
userId、timestamp、message(含行号/参数插值)等字段会导致几乎每条错误都自成一组,失去分组意义; - 保存前校验:非法 JSONPath 会在提交时被服务端拒绝(
jsonpath.New校验失败),配置错误不会静默生效。
与相邻能力的协同
错误分组并非孤立功能,它与仓库中的以下机制协同工作:
- 栈帧过滤:在分组之前,backend/errorgroups/filtering.go 的
IsErrorTraceFiltered会根据项目设置FilterChromeExtension过滤掉来自浏览器扩展(chrome-extension://、moz-extension://前缀)的栈帧,避免扩展噪音污染分组,相关行为有 filtering_test.go 覆盖。 - 嵌入向量分组(Embeddings):当工作区开启
ErrorEmbeddingsGroup时,后端会优先尝试基于大模型嵌入向量(GTE-Large-Embedding)的语义匹配(backend/public-graph/graph/resolver.go),相似度超过阈值才复用;否则回退到本文所述的经典分组流程(ErrorGroupingMethodClassic)。 - 错误事件去重缓存:
HandleErrorAndGroup会先按“事件 + 未转换堆栈”的GetDedupingKey做精确去重缓存(backend/errorgroups/fingerprint.go),缓存命中时直接复用既有分组结果,显著降低重复上报的分组开销。
小结
highlight.io 的错误分组围绕“消息相等优先、栈帧结构相似兜底、JSONPath 自定义增强”三层机制展开:
- 默认匹配:相同的错误消息,或“顶部栈帧一致 + 后 4 帧中 3 帧一致”;
- 栈帧指纹:META(文件名/函数名/行号/列号)与 CODE(源码及上下文,依赖 Source Map)两类指纹参与打分;
- JSONPath 分组规则:在项目设置中添加
$.type等表达式,即可按 JSON 结构化字段自定义分组维度,实现跨行号、跨消息的错误归并。
底层实现中,所有匹配都归一化为“指纹 + 加权打分”的 SQL 查询(GetTopErrorGroupMatch),顶帧 10 分、后续帧每帧 1 分、消息全等 100 分、JSONPath 指数级加权,最终以最低相似度阈值决定“并入已有组”还是“新建错误组”。理解这套机制,你就能精确控制错误聚合的粒度,让错误面板真正服务于定位根因,而不是淹没在重复报错里。
- 可观测性
- 后端
【免费下载链接】highlight
highlight.io: The open source, full-stack monitoring platform. Error monitoring, session replay, logging, distributed tracing, and more.
相关推荐
如何在Windows系统中实现苹果苹方字体的跨平台部署指南
如何在Windows系统中实现苹果苹方字体的跨平台部署指南 引言:跨平台字体一致性的技术挑战 在当今多平台数字环境中,字体显示的一致性已成为用户体验设计的关键要
前端PostHog Error Tracking 分组嘈杂错误实战:从指纹乱象到 Merge + Grouping Rule 的完整治理方案
PostHog Error Tracking 分组嘈杂错误实战:从指纹乱象到 Merge + Grouping Rule 的完整治理方案 本文是一份面向 Pos
数据分析后端前端数据可视化大数据Symfony消息路由:基于规则的消息分发机制
Symfony消息路由:基于规则的消息分发机制 在构建复杂的Web应用时,消息队列(Message Queue)是解耦系统组件、提高应用可扩展性的关键技术。Sy
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考