ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Sentry Related Issues 关联机制解析:同根因与同 Trace 的 Group 自动关联实现

Sentry Related Issues 关联机制解析:同根因与同 Trace 的 Group 自动关联实现 Sentry Related Issues 关联机制解析同根因与同 Trace 的 Group 自动关联实现【免费下载链接】sentryDeveloper-first error tracking and performance monitoring项目地址: https://gitcode.com/GitHub_Trending/sen/sentry导读本文基于 Sentry 开源仓库中src/sentry/issues/related/模块的设计文档与源码系统讲解 Related Issues 功能它如何在以 fingerprint 唯一化 Group 之后通过同根因same root cause与同 Tracetrace connected两类启发式把不同的 Issue 关联起来满足用户把相关错误放在一起批量处理的诉求。读完本文你将掌握该模块的两大分析算法、related-issues接口的请求/响应契约、权限与限流约束以及对应的测试验证方式。为什么需要 Related Issues在 Sentry 中一个问题Issue / Group并不是按错误发生次数创建的而是按事件的唯一指纹fingerprint聚合而成——指纹通常来自事件的堆栈或消息等信息。也就是说任何来自代码的改动都可能导致两个本来同源的问题分裂成两个不同的 Group。正如模块 README 在开篇指出的Issues in Sentry are created based on unique fingerprints based on the information from an event (e.g. the stack trace or the message). The Related Issues feature associates different issues based on the heuristics we will describe. This satisfies the desire of many customers to act on various issues together.Related Issues 的目标正是在指纹划定的分组之外用另一套启发式heuristics把彼此相关的 Group 关联起来让用户可以对一批同源问题统一行动例如一起标记、一起分配、一起处理。在设计的远期规划中Sentry 还提出了 super groups 的 RFC 方向用更宏观的超组概念统一承载这类关联需求——在本仓库当前的实现中则先落地为下面两种具体的关联类型。该功能由related/模块与配套接口共同承担模块目录结构如下src/sentry/issues/related/ ├── README.md # 设计说明本文主体文档 ├── __init__.py # 空文件 ├── same_root_cause.py # 启发式一同根因分析 └── trace_connected.py # 启发式二同 Trace 分析模块的入口被src/sentry/issues/endpoints/related_issues.py中的RelatedIssuesEndpoint调用因此两种启发式最终都通过 HTTP API 对外暴露。启发式一同一根因Same Root CauseREADME 对该启发式的描述很精炼In many cases, a bug or an environmental failure will create many Sentry issues which can be merged.即一个 bug 或一次环境故障往往会派生出多个可被合并的 Sentry Issue。例如一次超时故障在多个 SDK / 环境上重复出现、或一次配置变更导致同一条异常以不同上下文上报时就会在同一个项目里产生多个 Group而它们本质上共享同一个根因。same_root_cause分析就是用来识别并合并这类 Group 的。分析入口与主流程same_root_cause.py定义了两个核心函数def same_root_cause_analysis(group: Group) - tuple[list[int], dict[str, str]]: Analyze and create a group set if the group was caused by the same root cause. # Querying the data field (which is a GzippedDictField) cannot be done via # Djangos ORM, thus, we do so via compare_groups project_groups RangeQuerySetWrapper( Group.objects.filter(projectgroup.project_id).exclude(idgroup.id), limit100, ) same_error_type_groups [g.id for g in project_groups if compare_groups(g, group)] return same_error_type_groups or [], {} def compare_groups(groupA: Group, groupB: Group) - bool: return match_criteria( {title: groupA.title, type: groupA.get_event_type()}, {title: groupB.title, type: groupB.get_event_type()}, ) def match_criteria(a: dict[str, str | None], b: dict[str, str | None]) - bool: # XXX: In future iterations we will be able to use similar titles rather than an exact match return a[type] b[type] and a[title] b[title]主流程可以拆解为四个步骤限定比较范围取出与目标 Group 同项目projectgroup.project_id且排除自身.exclude(idgroup.id)的全部 Group并通过RangeQuerySetWrapper设定迭代上限limit100即单次最多比较 100 个 Group逐对比较对每个候选 Group 调用compare_groups进行判断收集结果把判定为同根因的 Group id 放入列表返回无结果时返回空列表与空的meta返回结构函数签名与 trace 分析保持一致统一返回(list[int], dict[str, str])二元组。为什么用 Python 逐条比较而不是 SQL源码注释解释了关键设计取舍Querying the data field (which is a GzippedDictField) cannot be done via Djangos ORM, thus, we do so via compare_groupsGroup 的事件类型等元信息存储在dataGzippedDictField即经过 gzip 压缩的字典字段中Django ORM 无法直接对这类字段做条件过滤因此实现上采用ORM 全量取回 Python 内存中比较的方式。匹配规则类型 标题的精确相等match_criteria给出了当前版本的判等逻辑也是整个启发式最核心的规则比较维度取值来源比较方式typegroup.get_event_type()精确相等如errortitlegroup.title精确相等区分大小写的字符串比较源码中的XXX注释也明确了演进方向未来的迭代希望引入相似标题similar titles而非严格相等以便覆盖拼写抖动、参数化差异等场景——当前快照仍是精确匹配是刻意保守的初始版本。测试中的同根因场景在tests/sentry/issues/endpoints/test_related_issues.py中测试数据非常直观地展示了同根因关联的判定边界error_type ApiTimeoutError error_value Timed out attempting to reach host: api.github.com group self.create_group(dataself._data(error_type, error_value)) groups_data [ self._data(ApiError, error_value), # 类型不同 → 不关联 self._data(error_type, Unreacheable host: api.github.com), # 标题不同 → 不关联 self._data(error_type, ), # 标题为空 → 不关联 ] ... related self.create_group(dataself._data(error_type, error_value)) # 类型、标题都相同 → 关联 response self.get_success_response(qs_params{type: same_root_cause}) assert response.json() {type: same_root_cause, data: [related.id], meta: {}}可见只有event type 与 title 都完全一致的 Group 才会被返回为相关其余三个近亲同值不同类型、同类型不同值、空值均被排除。启发式二同 Trace 连接Trace Connected如果说同根因解决的是长得像的问题那么 trace connected 解决的则是在一起跑过的问题同一个分布式事务trace中发生的多个错误往往指向同一次请求链路的根因。一个前端页面调用后端、再调用第三方 API 的链路上任意一环出错都会在各自项目内产生 Issue而这些 Issue 共享同一个trace_id——trace_connected启发式正是基于这一点把它们关联起来。调用流程从 Group 到事件再到 Tracetrace_connected.py中的入口函数trace_connected_analysis负责把 Group 上下文解析成一个具体的 Eventdef trace_connected_analysis( group: Group, projects: list[Project], event_id: str | None None, project_id: int | None None, ) - tuple[list[int], dict[str, str]]:event_id/project_id是可选参数由此区分两条事件获取路径指定事件路径当调用方显式传入event_id时必须先断言project_id非空再通过eventstore.backend.get_event_by_id(project_id, event_id, group_idgroup.id)取回事件。随后连续断言事件必须存在assert event is not None事件必须确实属于当前 Groupassert event.group.id group.id。目的正如注释所述如果请求指定了具体事件调用方应当收到错误而非静默的降级结果从而确保分析的对象确实属于这个 Group。默认路径未指定事件时调用group.get_recommended_event_for_environments()取该 Group 的推荐事件与当前环境选择相符的代表性事件。拿到事件后统一交给trace_connected_issues(event, projects)继续执行若取不到事件则在meta中记录No event found for group.并返回空结果。Trace 关联的核心查询两条实现路径trace_connected_issuestrace_connected.py先检查事件是否携带trace_id——没有则写入meta[error]并提前返回有则并行执行两条数据通路路径一SnubaDiscover实现——_trace_connected_issues_snubastart, end default_start_end_dates() # Today to 90 days back query DiscoverQueryBuilder( Dataset.Events, {start: start, end: end, organization_id: org_id, project_id: project_ids}, queryftrace:{trace_id}, selected_columns[id, issue.id], orderby[id], # 不加 timestamp 排序避免 Snuba 拆分时间范围产生多次查询 limit100, configQueryBuilderConfig(auto_fieldsFalse), )实现要点时间范围使用default_start_end_dates()即从今天回溯 90 天用 Discover 查询语法trace:{trace_id}直接按 trace id 过滤选区为id与issue.id注释特别解释了orderby[id]的动机不要用 timestamp 排序否则 Snuba 需要拆分时间区间执行多次查询结果通过bulk_snuba_queries批量提交referrer 固定为Referrer.API_ISSUES_RELATED_ISSUES对应src/sentry/snuba/referrer.py中定义的api.issues.related_issues便于后端对这条查询链路做用量统计与配额治理最后用exclude_group_id即当前 Group 自身过滤掉自己避免自己关联自己。路径二EAPEvent Analytics Platform实现——_trace_connected_issues_eapgroup_ids get_group_ids_for_trace_id( snuba_paramssnuba_params, trace_idtrace_id, referrerReferrer.API_ISSUES_RELATED_ISSUES.value, occurrence_categoryOccurrenceCategory.ERROR, limit100, ) group_ids.discard(exclude_group_id)这是面向新一代事件分析平台 EAP 的同名实现底层封装位于src/sentry/search/eap/occurrences/common_queries.py的get_group_ids_for_trace_id并通过occurrence_categoryOccurrenceCategory.ERROR只召回 error 类型的 occurrence同样以limit100封顶并剔除自身。新旧数据通路如何协同experiment 对照两条通路不会盲目取其一而是由src/sentry/utils/rollout.py中的EAPOccurrencesComparator统一调度if EAPOccurrencesComparator.should_check_experiment(issues.related.trace_connected_issues): eap_results _trace_connected_issues_eap(...) issues EAPOccurrencesComparator.check_and_choose( snuba_results, eap_results, issues.related.trace_connected_issues, is_experimental_data_nullishlen(eap_results) 0, reasonable_match_comparatorlambda snuba, eap: eap.issubset(snuba), debug_context{...}, )即当 rollout 配置允许该 experimentcallsite 名为issues.related.trace_connected_issues时同时跑 EAP 结果并与之对照——对照原则是EAP 结果应当是 Snuba 结果的子集eap.issubset(snuba)并会携带 trace_id、organization、project、group 等debug_context用于线上排查。响应中的 meta 信息每次成功的 trace 分析都会在meta中回传上下文event_id实际用于分析的事件 idtrace_id命中的 trace id若事件没有 trace则meta含error: No trace_id found in event.。这让前端可以把相关 Issue与证据trace 链接一同展示给用户。API 契约如何调用 related-issues两种启发式统一通过RelatedIssuesEndpoint暴露路由在src/sentry/api/urls.py中注册为GET /api/0/organizations/{organization_slug}/issues/{issue_id}/related-issues/?type...查询参数由RequestSerializerrelated_issues.py定义并校验参数类型必填取值/说明typechoice是same_root_cause或trace_connectedevent_idstring否指定用于 trace 分析的具体事件project_idinteger否配合event_id使用定位事件所在项目参数不合法时返回400测试test_validation验证了type非法值与非整数project_id的错误响应。分发逻辑与权限过滤get处理器依据type分流related_issues.pysame_root_cause直接调用same_root_cause_analysis(group)trace_connected先取组织下所有状态为 active 的项目再用filter_projects_by_permissions按当前请求用户的权限过滤include_all_accessibleTrue随后调用trace_connected_analysis(...)。这一点与测试中的行为吻合无成员权限的受限用户查不到跨项目结果test_trace_connected_excludes_projects_member_cannot_access而组织开放成员制open membership下可跨团队项目返回关联结果test_trace_connected_open_membership_spans_projects——注释明确写道 A trace can span any project in the organization。响应结构{ type: same_root_cause | trace_connected, data: [group_id, ...], meta: { event_id: ..., trace_id: ... } }data关联 Group id 数组相关测试断言见test_related_issues.py与#L66-L74meta供上层展示的证据信息当 trace 相关的断言失败如指定事件不属于该 Group时捕获AssertionError并返回400 {}。限流与发布状态该端点是一次典型的重型分析接口代码对此设置了严格的限流related_issues.py维度窗口限值IP5 秒15 次USER5 秒15 次ORGANIZATION1 秒15 次同时其publish_status为EXPERIMENTAL实验性 API语义与返回结构可能变化并已被deprecated装饰器标记为过期——suggested_api指向一个组织级的related-issues形态对应src/sentry/api/urls.py注册的组织级路由sentry-api-0-organization-related-issues。这意味着当前快照里存在从issue 级向组织级演进的双轨过渡布局新实现应优先考虑组织级接口。测试与验证方法仓库为 Related Issues 提供了完整的接口级测试基座tests/sentry/issues/endpoints/test_related_issues.py继承APITestCase, SnubaTestCase, TraceTestCase覆盖以下行为矩阵测试用例验证点test_same_root_related_issues只有 type 与 title 完全一致的 Group 才会被关联test_trace_connected_errors同一 trace 跨项目产生的两个 Group 互为关联meta返回推荐事件与 trace idtest_trace_connected_errors_specific_event显式传event_idproject_id时按指定事件分析test_trace_connected_excludes_projects_member_cannot_access无权限成员看不到其它项目的关联 Grouptest_trace_connected_open_membership_spans_projects开放成员制组织可跨项目返回关联test_validation非法type/project_id返回 400 及字段级错误其中TraceTestCase/load_errors负责构造跨项目共享同一trace_id的分布式错误事件是 trace 关联语义能被稳定复现的关键测试基建。小结与演进方向Related Issues 模块的本质是在fingerprint 精确聚合之上叠加更宽泛的关联启发式为用户提供可批量行动的相关 Issue 集合。当前快照的定位非常克制Same root cause限定同项目、上限 100 个候选仅以event type title精确匹配误报率低但召回有限源码已预留相似标题的演进注释Trace connected时间窗 90 天、上限 100以trace:{id}检索同链路错误兼顾了旧版 Snuba 通路与新一代 EAP 通路的实验对照迁移。模块 README 提到Sentry 后续计划引入 super groups超组机制届时这些启发式关联结果将有望被收编进更统一的分组模型。对想要二次开发或深度理解 Sentry 分组体系的读者建议沿着 模块 README → 同根因实现 → Trace 实现 → 接口实现 → 接口测试 这条链路继续阅读。【免费下载链接】sentryDeveloper-first error tracking and performance monitoring项目地址: https://gitcode.com/GitHub_Trending/sen/sentry创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表