网络技术

你的位置:【欧冠体育竞猜 手机网页登陆】 > 网络技术 > Sentry 开发者贡献指南


Sentry 开发者贡献指南

发布日期:2022-08-06 18:44    点击次数:83

本文档的目标是将 Sentry SDK 中性能监控功用的蜕变置于凹凸文中。我们首先总结了怎么样将性能监控增加到 Sentry 和 SDK, 尔后我们探究 identified issues(已肯定的成就) 排汇的经历辅导以及经管这些成就的运动。

介绍

早在 2019 年终,Sentry 就起头查验测验向 SDK 增加跟踪功用。 Python 和 JavaScript SDK 是策画和开发第一个见解的测试平台。见解验证于 2019 年 4 月 29 日 宣布, 并于 2019 年 5 月 7 日交付给 Sentry。 Python 和 JavaScript 是不言而喻的抉择,因为它们准许我们履行检测 Sentry 自身的后端和前端。

https://github.com/getsentry/sentry-python/pull/342 https://github.com/getsentry/sentry-javascript/pull/1918 https://github.com/getsentry/sentry-python/releases/tag/0.7.13 https://github.com/getsentry/sentry/pull/12952

请留心,上述事变与 OpenCensus 和 OpenTracing 并吞组成 OpenTelemetry 是同时代的。 Sentry 的 API 和 SDK 实现自创了 OpenTelemetry 1.0 从前版本的灵感,并联结了我们自身的主见主张。譬如,我们的 Span 形态列表与 2019 年底阁下在 OpenTelemetry 标准中可以或许找到的成家。

https://medium.com/opentracing/a-roadmap-to-convergence-b074e5815289 https://github.com/getsentry/relay/blob/55127c75d4eeebf787848a05a12150ee5c59acd9/relay-co妹妹on/src/constants.rs#L179-L181

应用 API 后,性能监控支持随后扩张到别的 SDK。Sentry 的性能监控 经管规划于 2020 年 7 月宽泛可用。 OpenTelemetry 的跟踪标准 1.0 版于 2021 年 2 月宣布。

https://blog.sentry.io/2020/07/14/see-slow-faster-with-performance-monitoring https://medium.com/opentelemetry/opentelemetry-specification-v1-0-0-tracing-edition-72dd08936978

我们最初的实现重用了我们现有的舛误报告机制:

Event type 扩张了新字段。这意味着我们可以或许减省时光并倏地起头向 Sentry 发送事宜,而不是策画和实现全新的摄入管道,这一次,不是 error,而是一种新的 transaction 事宜范例。 https://develop.sentry.dev/sdk/event-payloads/ 因为我们只是发送一种新型事宜,因而也重用了 SDK 传输层。 因为我们同享摄入管道(ingestion pipeline),这意味着我们同享存储以及发生在全体事宜上的处理惩罚的良多部份。

我们的实现蜕变成大白夸大 Transaction 和 Span 之间的差异。部份启事是重用 Event 接口的恶浸染。

Transaction 与客户孕育发生了杰出的共鸣。他们准许突出表今世码中的首要事变块,譬如阅读器页面加载或 http 服务器哀告。客户可以或许查察和阅读 transaction 列表,而在 transaction 中,span 为更细粒度的事变单元供应详细的时光安插。

在下一节中,我们将探究今后模型的一些弱点。

已肯定的成就

诚然统一 SDK 架构(hub、client、scope) 和 transaction ingestion 模型的重用有其所长,但经历提醒了一些我们将其分为两类的成就。

https://develop.sentry.dev/sdk/unified-api/

第一组与 scope 传播无关,本质上是肯定 今后 scope 是什么的才能。用户代码中的手动检测以及 SDK 集成中的自动检测都需求此操作。

第二组是与用于将 transaction 数据从 SDK 发送到 Sentry 的 wire 项目相干的成就。

Scope 传播

该成就由 getsentry/sentry-javascript#3751 跟踪。

https://github.com/getsentry/sentry-javascript/issues/3751

Unified SDK 架构 根抵上是基于每个并发单元存在一个 hub,每个 hub 有一堆 client 和 scope 对。

https://develop.sentry.dev/sdk/unified-api/

Client 生活生涯设置并担当经由过程 transport 向 Sentry 发送数据,而 scope 生活生涯附加到传误事失事宜(譬如 tag 和 breadcrumb)的凹凸文数据。

每个 hub 都晓得今后的 scope 是什么。它一直是仓库顶部的 scope。费力的部份是 “per unit of concurrency(每单元并发)” 有一个 hub。

譬如,JavaScript 是具有事宜循环和异步代码执行的复线程。没有标准的编制来承载跨异办法用事变的凹凸文数据。因而,关于 JavaScript 阅读器应用顺序,只要一个全局 hub 同享用于同步和异步代码。

近似的环境出当初 Mobile SDK 上。用户期冀凹凸文数据(譬如 tags、current user 是什么、 breadcrumbs 以及存储在 scope 上的别的信息)可以或许从任何线程获取和设置。因而,在这些 SDK 中,只要一个全局 hub。

在这两种环境下,当 SDK 必须处理惩罚 reporting errors 时,通通都相对较好。随着跟踪 transaction 和 span 的额外义务,scope 变得不适合存储今后的 span,因为它限定了并发 span 的存在。

关于阅读器 JavaScript,一个兴许的经管规划是应用 Zone.js,Angular 框架的一部份。首要寻衅是它增加了包的大小,并且兴许会无意中影响终究用户应用顺序,因为它对 JavaScript 运行时引擎的关键部份举行了猴子修补(monkey-patches)。

https://github.com/angular/angular/blob/master/packages/zone.js/README.md

当我们查验测验为手动检测创立更俭朴的 API 时,scope 传播成就变得尤为分明。这个主见主张是果真一个 Sentry.trace 函数,该函数将隐式传播 tracing 和 scope 数据, 并支持同步和异步代码的深度嵌套。

举个例子,假设有人想测量征采 DOM 树需求多长时分。Tracing(跟踪) 此操作将以下所示:

await Sentry.trace(   {     op: 'dom',     description: 'Walk DOM Tree',   },   async () => await walkDomTree() ); 

应用 Sentry.trace 功用,用户在增加计时数据时无须耽心留存对准确 transaction 或 span 的引用。用户可以或许在 walkDomTree 函数中自由创立子 Span,Span 将在准确的条理组织中排序。

现实 trace 函数的实现相对俭朴 (拜会具有示例实现的 PR)。然而,相识异步代码和全局集成中的今后 span 是一个还没有降服的寻衅。

https://github.com/getsentry/sentry-javascript/pull/3697/files#diff-f5bf6e0cdf7709e5675fcdc3b4ff254dd68f3c9d1a399c8751e0fa1846fa85dbR158

下列两个示例综合了 scope 传播成就。

没法肯定今后 Span

推敲一些需求获取对今后 span 的引用的自动检测代码,在这类环境下,手动 scope 传播不成用。

// SDK code function fetchWrapper(/* ... */) {   /*     ... some code omitted for simplicity ...   */   const parent = getCurrentHub().getScope().getSpan(); // <1>   const span = parent.startChild({     data: { type: 'fetch' },     description: `${method} ${url}`,     op: 'http.client',   });   try {     // ...     // return fetch(...);   } finally {     span.finish();   } } window.fetch = fetchWrapper;  // User code async function f1() {   const hub = getCurrentHub();   let t = hub.startTransaction({ name: 't1' });   hub.getScope().setSpan(t);   try {     await fetch('https://example.com/f1');   } finally {     t.finish();   } } async function f2() {   const hub = getCurrentHub();   let t = hub.startTransaction({ name: 't2' });   hub.getScope().setSpan(t);   try {     await fetch('https://example.com/f2');   } finally {     t.finish();   } } Promise.all([f1(), f2()]); // run f1 and f2 concurrently 

在上面的例子中,几个并发的 fetch 哀告触发了 fetchWrapper helper 的执行。行 <1> 必须兴许痛处今后的执行流程窥察到差异的 span,导致以下两个 span 树:

t1 \   |- http.client GET https://example.com/f1 t2 \   |- http.client GET https://example.com/f2 

这意味着,当 f1 运行时,parent 必须引用 t1,而当 f2 运行时,parent 必须是 t2。不幸的是,上面的全体代码都在争先恐后地更新和读取单个 hub 实例,因而窥察到的 span 树不是肯定性的。譬如,终局兴许舛误地为:

t1 t2 \   |- http.client GET https://example.com/f1   |- http.client GET https://example.com/f2 

作为没法准确肯定今后 span 的恶浸染, fetch 集成的体现实现(和别的)在JavaScript 阅读器 SDK 当抉择创立 flat transactions, 个中全体子 span 都是 transaction 的间接子代(而不是具有适合的多级树组织)。

https://github.com/getsentry/sentry-javascript/blob/61eda62ed5df5654f93e34a4848fc9ae3fcac0f7/packages/tracing/src/browser/request.ts#L169-L178

请留心,别的跟踪库也面临一样的寻衅。在 OpenTelemetry for JavaScript 中有几个(在开放时)成就与肯定父跨度和准确的凹凸文传播(蕴含异步代码)相干:

假设应用多个 TracerProvider 实例,则凹凸文走漏 #1932

https://github.com/open-telemetry/opentelemetry-js/issues/1932

怎么样在不通报 parent 的环境下创立嵌套 span #1963

https://github.com/open-telemetry/opentelemetry-js/issues/1963

嵌套的子 span 没有失去准确的父级 #1940

https://github.com/open-telemetry/opentelemetry-js/issues/1940

OpenTracing shim 不会改变凹凸文 #2016

https://github.com/open-telemetry/opentelemetry-js/issues/2016

Http Span 未链接/未设置父 Span #2333

https://github.com/open-telemetry/opentelemetry-js/issues/2333

互相抵触的数据传播预期

每当我们增加前面探究过的 trace 函数,或许只是查验测验应用 Zones 经管 scope 传播时,就会出现预期抵触。

今后的 span 与 tags、breadcrumbs 等一起存储在 scope 中的现实使数据传播变得杂遝, 因为 scope 的某些部份旨在仅传播到外部函数调用中(譬如,tags), 而其余人预计会传播回调用者(譬如,breadcrumbs),尤为是在出现 error 时。

这是一个例子:

function a() {   trace((span, scope) => {     scope.setTag('func', 'a');     scope.setTag('id', '123');     scope.addBreadcrumb('was in a');     try {       b();     } catch(e) {       // How to report the SpanID from the span in b?     } finally {       captureMessage('hello from a');       // tags: {func: 'a', id: '123'}       // breadcrumbs: ['was in a', 'was in b']     }   }) }  function b() {   trace((span, scope) => {     const fail = Math.random() > 0.5;     scope.setTag('func', 'b');     scope.setTag('fail', fail.toString());     scope.addBreadcrumb('was in b');     captureMessage('hello from b');     // tags: {func: 'b', id: '123', fail: ?}     // breadcrumbs: ['was in a', 'was in b']     if (fail) {       throw Error('b failed');     }   }); } 

在上面的示例中,假设 error 在调用仓库中冒泡,我们停留兴许报告 error 发生在哪一个 span(经由过程引用 SpanID)。我们停留有面包屑来形貌发生的通通,不管哪一个 Zones 正在执行, 我们停留在外部 Zone 中设置一个 tag 来笼盖来自父 Zone 的同名 tag, 同时继承来自父 Zone 的全体别的 tag。每个 Zone 都有自身的 "current span"。

全体这些差异的期冀使得很难以一种可以或许理解的编制重用今后的 scope 见解、面包屑的记载编制以及这些差异的见解怎么样互相浸染。

最后,值得留心的是,在不破坏现有 SDK API 的环境下,重组 scope 打点的改观很兴许没法实现。现有的 SDK 见解 — 如 hubs、scopes、breadcrumbs、user、tags 和 contexts — 都必须从头建模。

Span 摄入模型

推敲由下列 span 树形貌的跟踪:

F* ├─ B* │  ├─ B │  ├─ B │  ├─ B │  │  ├─ S* │  │  ├─ S* │  ├─ B │  ├─ B │  │  ├─ S* │  ├─ B │  ├─ B │  ├─ B │  │  ├─ S*  where F: span created on frontend service B: span created on backend service S: span created on storage service 

此跟踪分化了 3 个被检测的服务,当用户单击网页上的按钮 (F) 时,后端 (B) 执行一些事变,尔后需求对存储服务 (S) 举行屡次查询。位于给定服务入口点的 Span 标有 * 以默示它们是 transaction。

我们可以或许经由过程这个例子来相比和理解 Sentry 的 span 摄入模型与 OpenTelemetry 和别的近似跟踪体系应用的模型之间的差异。

在 Sentry 的 span 摄入模型中,属于 transaction 的全体 span 必须在单个哀告中一起发送。这意味着在全副 B* transaction 时期,全体 B span 都必须生活生涯在内存中,蕴含在上流服务(示例中的存储服务)上花费的时光。

在 OpenTelemetry 的模型中,span 在实现时被一起批处理惩罚,并且一旦 a) 批次中有必定数量标 span 或 b) 过了必定的时光就会发送批次。在我们的示例中,这兴许意味着前 3 个 B 跨度将一起批处理惩罚并发送, 而第一个 S* 事件仍在存储服务及第行。随后,别的 B span 将一起批处理惩罚并在实现时发送,直到终究 B* transaction span 也被发送。

诚然 transaction 作为将 span 组合在一起并探索 Sentry 中感兴致的操作的一种编制特殊有效, 但它们而今存在的模式会带来额外的认知包袱。 SDK 回护人员和终究用户在编写检测代码时都必须相识并在 transaction 或 span 之间举行抉择。

在今后的摄入模型中已经肯定了接上去几节中的成就,网络技术并且都与这类二分法无关。

事件的宏壮 JSON 序列化

在 OpenTelemetry 的模型中, 全体跨度都服从像同的逻辑项目。用户和检测库可以或许经由过程将 key-value 属性附加就任何 span 来为其供应更多含义。 wire 和谈应用 span 列表将数据从一集体系发送到另外一集体系。

https://github.com/open-telemetry/opentelemetry-proto/blob/ebef7c999f4dea62b5b033e92a221411c49c0966/opentelemetry/proto/trace/v1/trace.proto#L56-L235

与 OpenTelemetry 差异,Sentry 的模型对两种范例的 span 举行了严厉判别:transaction span(平日称为 transactions)和 regular span。

在内存中,transaction span 和 regular span 有一个差异:transaction span 有一个额外的属性,即 transaction name。

然则,当序列化为 JSON 时,差异更大。 Sentry SDK 以直激情亲切似于内存中的 span 的项目将通例 span 序列化为 JSON。相比之下,transaction span 的序列化需求将其 span 属性晖映到 Sentry Event (最初用于 report errors,扩张为专门用于 transactions 的新字段),并将全体子 span 作为列表嵌入 Event 中。

Transaction Span 获取 Event 属性

当 transaction 从其内存默示转换为 Event 时, 它会获取更多没法分派给 regular span 的属性, 譬如 breadcrumbs, extra, contexts, event_id, fingerprint, release, environment, user 等。

生命周期钩子

Sentry SDK 为 error 事宜果真了一个 BeforeSend hook,答应用户在将事宜发送到 Sentry 从前编削和/或扬弃事宜。

当引入新的 transaction 范例事宜时,很快就抉择此类事宜不会经由过程 BeforeSend hook,首要有两个启事:

预防用户代码寄托 transaction 的两重模式(偶尔看起来像一个 span,偶尔像一个 event,如前几节所述); 为了预防现有的 BeforeSend 函数在编写时只推敲到 error 而纷扰扰攘侵略 transaction,不管是意外埠改变它们、齐全扬弃它们,照旧导致一些别的意想不到的恶浸染。

然而,也很分明需求某种模式的 lifecycle hook,以答应用户执行诸如更新 transaction 名称之类的操作。

我们终究告竣为了中央立场,即经由过程应用 EventProcessor(一种更通用的 BeforeSend 模式)来准许改观/扬弃 transaction 事宜。这经由过程在数据来到 SDK 从前让用户登时拜访他们的数据来经管成就,但它也出弱点,它比 BeforeSend 应用起来更宏壮,并且还表露了从未谋略走漏的 transaction 二元性。

相比之下,在 OpenTelemetry 中,span 经由过程 span processor,这是两个生命周期钩子:一个是在 span 起头时,一个是在它终止时。

嵌套事件

Sentry 的摄入模型不是为服务中的嵌套 transaction 而策画的。Transaction 旨在标记服务转换。

在实际中,SDK 没法预防 transaction 嵌套。终究终局兴许会让用户认为惊异,因为每笔 transaction 都市起头一棵新树。联络纠葛这些树的仅有编制是经由过程 trace_id。

Sentry 的计费模型是针对每个事宜的,不管是 error 事宜照旧 transaction 事宜。这意味着 transaction 中的 transaction 会生成两个可计省事宜。

在 SDK 中,在 transaction 及第行 transaction 将导致外部 span 被萦绕它们的最内层 transaction “吞噬”。在这些环境下,创立 span 的代码只会将它们增加到两个 transaction 之一,从而导致另外一个 transaction 中的检测空隙。

Sentry 的 UI 并不是旨在以有效的编制处理惩罚嵌套 transaction。当查察任何一个 transaction 时,就彷佛 transaction 中的全体别的 transaction 都不存在(树视图上没有间接默示别的 transaction)。有一个 trace view 功用来可视化同享一个 trace_id 的全体 transaction, 但 trace view 仅经由过程体现 transaction 而不是子 span 来供应跟踪的概述。假设不先拜访某个 transaction,就没法导航到 trace view。

关于这类环境(伪代码),用户对 UI 中的期冀也存在混合:

# if do_a_database_query returns 10 results, is the user #   - seeing 11 transactions in the UI? #   - billed for 11 transactions? #   - see spans within create_thumbnail in the innermost transaction only? with transaction("index-page"):     results = do_a_database_query()     for result in results:         if result["needs_thumbnail"]:             with transaction("create-thumbnail", {"resource": result["id"]}):                 create_thumbnail(result) 
跨度不克不迭存在于事件之外

Sentry 的追踪休会齐全萦绕着存在于 transaction 中的 trace 部份。这意味着数据不克不迭存在于 transaction 之外,纵然它存在于 trace 中。

假设 SDK 没有举行 transaction,则由 instrumentation 创立的 regular span 将齐全遗失。也就是说,这对 Web server 来说不是什么成就,因为自动检测的 transaction 随着每个传入哀告起头和终止。

Transaction 的哀告在前端(阅读器、移动和桌面应用顺序)上尤为具有寻衅性, 因为在这些环境下,自动检测的 transaction 不太牢靠地捕捉全体 span,因为它们在自动实现从前只延续无限的时光。

在 trace 以仅作为 span 而不是 transaction 举行检测的操作起头的环境下,会出现另外一个成就。在我们的 示例跟踪中,孕育发生 trace 的第一个 span 是因为单击按钮。假设按钮点击 F* 被检测为通例的 span 而不是 transaction,则很兴许不会捕捉来自前端的数据。然而,仍会捕捉 B 和 S span,导致不完备的踪迹。

在 Sentry 的模型中,假设一个 span 不是一个 transaction 并且没有作为 transaction 的祖先 span,那末该 span 将不会被摄入。反已往,这意味着在良多环境下,跟踪遗失了有助于调试成就的关键信息,特殊是在前端,transaction 需求在某个时分终止但执行兴许会延续。

自动和手动检测面临着抉择是起头 span 照旧 transaction 的寻衅,推敲到下列要素,抉择尤为费力:

假设没有 transaction,则 span 遗失。

假设已经存在 transaction,则存在嵌套事件成就。

缺乏 Web Vitals 测量

Sentry 的阅读器器材采集 Web Vitals 测量值。然则,因为这些测量值是应用自动检测的 transaction 作为载体发送到 Sentry 的,所以在自动 transaction 实现后由阅读器供应的测量值将遗失。

这会导致 transaction 遗失一些 Web Vitals 或对 LCP 等指标举行非终究测量。

前端事件延续时光不成靠

因为全体的数据都必须在一个 transaction 中。Sentry 的阅读器 SDK 为每个页面加载和每个导航创立一个 transaction。这些 transaction 必须在某个时光终止。

假设在 transaction 实现从前敞开阅读器选项卡并将其发送到 Sentry,则全体采集的数据都市遗失。因而,SDK 需求平衡遗失全体数据的危险与采集不完备和兴许不准确的数据的危险。

在窥察到最后一个流动(譬如传出的 HTTP 哀告)后余暇了一段时光后,Transaction 就实现了。这意味着页面加载或导航 transaction 的延续时光是一个相当随意的值,不必定能改进或与别的事件相比,因为它不克不迭准确代表任何详细和可理解的进程的延续时光。

我们经由过程将 LCP Web Vital 作为阅读器的默认性能指标来应对这一限定。然则,如上所述,LCP 值兴许会在终究肯定从前发送,因而这不是理想的经管规划。

内存缓冲影响服务器

如前所述,今后的摄入模型需求 Sentry SDK 来窥察内存中的完备 span 树。以恒定的并发 transaction 流运行的应用顺序将需求大量的体系资原本采集和处理惩罚跟踪数据。Web 服务器是出现此成就的典范案例。

这意味着记载 100% 的 span 和 100% 的 transaction 关于良多服务器端应用顺序来说是不成行的,因为所孕育发生的开销过高了。

没法批处理惩罚事件

Sentry 的摄入模型不支持一次摄入多个事宜。特殊是,SDK 不克不迭将多个 transaction 批处理惩罚为一个哀告。

因而,当多笔 transaction 险些同时实现时,SDK 需求为每个 transaction 收回零丁的哀告。这类动作在最佳的环境下是极度低效的,在最坏的环境下是对资源(如网络带宽和CPU周期)的重大且有成就的斲丧。

兼容性

Transaction Span 的不凡处理惩罚与 OpenTelemetry 不兼容。应用 OpenTelemetry SDK 检测现有应用顺序的用户没法轻松应用 Sentry 来获取和阐发他们的数据。

Sentry 确凿为 OpenTelemetry Collector 供应了一个 Sentry Exporter,然则,因为今后的摄入模型,Sentry Exporter 有一个首要的准确性限定。

https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/sentryexporter#known-limitations

总结

经由过程在 Sentry 中构建今后的跟踪实现,我们学到了良多。本文档试图捕捉良多已知的限定,以作为未来改进的根抵。

追踪是一个宏壮的主题,驯服这类宏壮性并不是易事。

第一组中的成就 - 与 scope propagation(浸染域传播) 相干的成就 - 是 SDK 及其策画编制独有的成就。经管这些成就将需求对全体 SDK 举行外部架构改观,蕴含从头策画面包屑等旧功用, 但举行此类改观是实现俭朴易用的 tracing helper(如可在任何凹凸文中事变并捕捉准确牢靠的性能数据的 trace 函数)的先决条件。请留心,此类改观险些必然意味着宣布新的首要 SDK 版本,这会破坏与现有版本的兼容性。

第二组中的成就 - 与 span ingestion model(跨度摄入模型) 相干的成就要宏壮很多,因为为经管这些成就所做的任何改观都市影响产品的更多部份,并且需求多个团队的谐和尽力。

 

尽管云云,对 ingestion model 举行改观将对产品孕育发生不成估计的积极影响,因为这样做会行进效劳,使我们兴许采集更大都据,并削减 instrumentation 的包袱。

 



上一篇:1964年,贺龙请杨勇吃饭,服务员拿来一瓶茅台,贺龙:谁操办的?
下一篇:Go 言语里怎么准确凿现罗列?答案藏着平易近间的源码里