顶级开源项目 Sentry 20.x JS-SDK 设计艺术(Unified API篇)

SDK 开发
- 顶级开源项目 Sentry 20.x JS-SDK 设计艺术(理念与设计原则篇)
- 顶级开源项目 Sentry 20.x JS-SDK 设计艺术(开发基础篇)
- 顶级开源项目 Sentry 20.x JS-SDK 设计艺术(概述篇)
系列
- Snuba:Sentry 新的搜索基础设施(基于 ClickHouse 之上)
- Sentry 10 K8S 云原生架构探索,Vue App 1 分钟快速接入
- Sentry(v20.x)玩转前/后端监控与事件日志大数据分析,使用 Helm 部署到 K8S 集群
- Sentry(v20.x) JavaScript SDK 三种安装加载方式
- Sentry(v20.x) JavaScript SDK 配置详解
- Sentry(v20.x) JavaScript SDK 手动捕获事件基本用法
- Sentry(v20.x) JavaScript SDK Source Maps详解
- Sentry(v20.x) JavaScript SDK 故障排除
- Sentry(v20.x) JavaScript SDK 1分钟上手性能监控
- Sentry(v20.x) JavaScript SDK 性能监控之管理 Transactions
- Sentry(v20.x) JavaScript SDK 性能监控之采样 Transactions
- Sentry(v20.x) JavaScript SDK Enriching Events(丰富事件信息)
- Sentry(v20.x) JavaScript SDK Data Management(问题分组篇)
统一的API
新的 Sentry SDK 应遵循 Unified API,使用一致的术语来指代概念。
本文档说明了 Unified API 是什么以及为什么它存在。
动机
Sentry 有各种各样的 SDK,这些 SDK 是由不同的开发人员根据不同的想法在过去几年里开发出来的。这导致了不同 SDK 的特性设置不同,使用不同的概念和术语,这导致了通常不清楚如何在不同的平台上实现相同的东西。
此外,这些 SDK 完全以通过 explicit clients 进行错误报告为中心,这意味着通常无法进行某些集成(例如面包屑 breadcrumbs)。
一般准则
- 我们希望所有
SDK API的语言/措辞统一,以辅助支持和文档编制,并使用户更轻松地在不同环境中使用Sentry。 - 在设计
SDK时,我们可以添加一些新的功能,而不是单纯的事件报告(transactions,APM等)。 - 设计具有相同
client实例的SDK,我们既可以通过依赖项注入等在运行时环境中自然工作,也可以使用隐式上下文分派给已经存在的clients和scopes,以挂接到大多数环境中。 这很重要,因为它允许事件将流程中其他集成的数据包括在内。 - 常见任务必须简单明了。
- 为了帮助第三方库,“non configured Sentry” 的情况需要快速处理(和延迟执行)。
- 通用
API需求在大多数语言中都是有意义的,并且一定不能依赖于超级特殊的构造。 为了使它更自然,我们应该考虑语言细节,并明确地支持它们作为替代方法(disposables,stack guards等)。
简化过的图解

术语
- minimal:一个单独的
“facade”包,它通过接口(interfaces)或代理(proxies)重新导出SDK功能的子集。该包不直接依赖于SDK,相反,如果没有安装SDK,它应该使每个操作都成为noop。这样一个包的目的是允许random库记录面包屑和设置上下文数据,同时不依赖SDK。 - hub:管理状态的对象。默认情况下可以使用隐含的
global thread local或类似的hub。Hubs可以手动创建。 - scope:
scope包含了应该与Sentry事件一起隐式发送的数据。它可以保存上下文数据、额外参数、级别覆盖、指纹等。 - client:
client是只配置一次的对象,可以绑定到hub。然后,用户可以自动发现client并分派对它的调用。用户通常不需要直接与client打交道。它们要么通过hub实现,要么通过static convenience functions实现。client主要负责构建Sentry事件并将其发送到transport。 - client options:是特定于语言和运行时的参数,用于配置
client。 这可以是release和environment,也可以是要配置的integrations,in-app works等。 - context:
Contexts为Sentry提供额外的数据。有特殊的上下文(user和类似的)和通用的上下文(runtime,os,device),等等。检查有效键的Contexts。注意:在旧的SDK中,您可能会遇到一个与上下文无关的概念,这个概念现在已被作用域弃用。 - tags:
Tags可以是任意string→ 可以搜索事件的string pairs。Contexts被转换为tags。 - extra:
client users附加的真正任意数据。这是一个已弃用的特性,但在可预见的未来将继续得到支持。鼓励用户使用上下文代替。 - transport:
transport是对事件发送进行抽象的客户端的内部构造。通常,transport在单独的线程中运行,并获取通过队列发送的事件。transport负责发送(sending)、重试(retrying)和处理速率限制(handling rate limits)。如果需要,transport还可能在重启过程中持久化未发送的事件。 - integration:向特定框架(
frameworks)或环境(environments)提供中间件(middlewares)、绑定(bindings)或钩子(hooks)的代码,以及插入这些绑定并激活它们的代码。集成的使用不遵循公共接口。 - event processors:针对每个事件运行的回调(
Callbacks)。他们可以修改并返回事件,或者可以为null。返回null将丢弃该事件,并且不会进一步处理。有关更多信息,请参见事件管道(Event Pipeline)。 - disabled SDK:大多数
SDK功能依赖于已配置的active client。当有transport时,Sentry 认为client是active的。否则,客户端是inactive的,SDK被认为是“disabled”。在这种情况下,某些回调函数,例如configure_scope或事件处理器(event processors),可能不会被调用。因此,面包屑(breadcrumbs)不会被记录下来。
"Static(静态)API"
静态 API 函数是最常见的面向用户的 API。用户只需导入这些功能,即可开始向 Sentry 发出事件或配置作用域。这些快捷方式功能应在包的顶级名称空间中导出。 他们在后台使用 hubs 和 scopes(有关更多信息,请参见并发性 Concurrency)(如果在该平台上可用)。请注意,下面列出的所有函数大部分都是 Hub::get_current().function 的别名。
init(options):这是每个SDK的入口点。
通常,这会创建(creates)/重新初始化(reinitializes)传播到所有新线程(new threads)/执行上下文(execution contexts)的global hub,或者为每个线程(per thread)/执行上下文(execution context)创建一个 hub。
接受 options(dsn 等),配置 client 并将其绑定到当前 hub 或对其进行初始化。应返回一个 stand-in,可用于 drain events(一次性)。
这可能会返回一个 handle 或 guard 来处理。如何实现这一点完全取决于 SDK。这甚至可能是一个 client,如果这对 SDK 有意义的话。在 Rust 中,它是一个 ClientInitGuard,在 JavaScript 中,它可以是一个带有可等待的 close 方法的 helper 对象。
您应该能够多次调用此方法,而第二次调用它既可以拆除先前的 client,也可以减少先前 client 的引用计数,等等。
多次调用只能用于测试。如果您在应用程序启动以外的任何时间调用 init,将会是 undefined。
用户必须调用一次 init,但允许使用禁用的 DSN 进行调用。
例如可能没有参数传递等。
此外,它还设置了所有默认的集成。
capture_event(event):接受一个已经组合好的事件,并将其调度到当前活动的中心。 事件对象可以是普通字典或类型化的对象,无论在SDK中更有意义。 它应尽可能遵循本机协议,而忽略平台特定的重命名(案例样式等)。capture_exception(error):报告error或exception对象。根据平台的不同,可能有不同的参数。最明显的版本只接受一个error对象,但在不传递error且使用当前exception的情况下也可能发生变化。capture_message(message, level):报告message。级别可以是可选的语言默认参数,在这种情况下,它应该默认为info。add_breadcrumb(crumb):向scope添加新的面包屑。 如果面包屑的总数超过max_breadcrumbs设置,则SDK应删除最旧的面包屑。这与Hub API的工作原理类似。如果禁用了SDK,它应该忽略breadcrumb。configure_scope(callback):可以重新配置scope对象调用的回调。这用于为相同范围内的未来事件附加上下文数据。last_event_id():应该返回当前作用域发出的最后一个事件ID。例如,这用于实现用户反馈对话框(feedback)。
并发
所有 SDK 都应具有并发安全上下文存储(concurrency safe context storage)的概念。 这意味着什么取决于语言。 基本思想是,SDK 的用户可以调用一种方法来为即将记录的所有事件安全地提供其他上下文信息。
在大多数语言中,这是作为 thread local stack 实现的,但在某些语言中(比如 JavaScript),它可能是全局的,因为假设这在环境中是有意义的。
以下是一些常见的并发模式:
- Thread bound hub:在这种模式下,每个
thread都有自己的“hub”,该hub在内部管理一系列作用域scopes。 如果遵循该模式,则一个thread(调用init()的线程)将成为“main” hub,该hub将用作新生成的线程的基础,该线程将获得基于主hub的hub(但又是独立的)。 - Internally scoped hub:在一些平台上,如
.NET ambient data是可用的,在这种情况下Hub可以内部管理作用域scopes。 - Dummy hub:在一些平台上,并发性
concurrency本身并不存在。在这种情况下,hub可能完全不存在,或者只是一个没有并发管理concurrency management的单例。
Hub
在正常情况下,hub 由一堆 clients 和 scopes 组成。
SDK 维护两个变量:main hub(一个全局变量)和 current hub(当前线程thead或执行上下文execution context的本地变量,有时也称为异步本地async local或上下文本地context local变量)
Hub::new(client, scope):使用给定的client和scope创建一个新的hub。client可以在hubs之间重用。scope应归hub所有(如有必要,请进行clone)Hub::new_from_top(hub)/ 或者原生构造函数重载native constructor overloads:通过克隆另一个hub的顶部堆栈top stack来创建新的hub。get_current_hub()/Hub::current()/Hub::get_current():全局函数或静态函数以返回当前(线程的)hub。get_main_hub()/Hub::main()/Hub::get_main():在主线程main thread是特殊的语言中(“Thread bound hub”模型),这会返回main thread的中心而不是当前线程current thread的中心。这可能并不存在于所有的语言中。Hub::capture_event/Hub::capture_message/Hub::capture_exception:捕获message / exception到capture event。capture_event将传递的event与scope数据合并,并分派给client。作为附加参数,它还需要一个提示。有关hint参数,请参见hints。Hub::push_scope():推送一个继承前一个数据的新作用域层new scope layer。 这应返回有意义的语言的disposable或stack guard。当使用 “内部作用域中心”internally scoped hub并发模型时,通常需要对此进行调用,否则可能会意外地错误共享作用域。Hub::with_scope(callback)(optional):在Python中,这可能是上下文管理器;在Ruby中,这可能是块函数。推动并弹出集成工作的scope。Hub::pop_scope(callback)(optional):只存在于没有更好的资源管理resource management的语言中。最好在push_scope的返回值上使用这个函数,或者使用with_scope。这有时也被称为pop_scope_unsafe,以表明不应该直接使用该方法。Hub::configure_scope(callback):使用对修改范围的可变引用来调用回调。 这也可以是具有它的语言(Python)中的with语句。如果没有active client绑定到该hub,则SDK不应调用回调。Hub::add_breadcrumb(crumb, hint):将面包屑添加到当前作用域。- 支持的参数应为:
- 创建面包屑的函数
- 已经创建的面包屑对象
- 面包屑列表(可选)
- 在没有基本重载形式的语言中,只有原始的面包屑对象
raw breadcrumb object应该被接受。 - 如果没有
active client绑定到该hub,则SDK应忽略面包屑。 - 有关
hint参数,请参见hints。
- 支持的参数应为:
Hub::client() / Hub::get_client()(optional):返回当前client或None的Accessor或getter。Hub::bind_client(new_client):将不同的client绑定到hub。如果hub也是init创建的client的所有者,那么如果hub是负责处理它的对象,则需要保留对它的引用。Hub::unbind_client()(optional):对于bind_client不接受空值的语言,可选的解绑定方法。Hub::last_event_id():应该返回当前scope发出的最后一个event ID。例如,这是用来实现用户反馈对话框feedback dialogs。Hub::run(hub, callback) hub.run(callback), run_in_hub(hub, callback)(optional):运行将hub绑定为当前hub的回调。
Scope
scope 包含了应该与 Sentry 事件一起隐式发送的数据。它可以保存上下文数据context data、额外参数extra parameters、级别覆盖level overrides、指纹fingerprints等。
用户可以通过全局函数 configure_scope 修改当前作用域(设置额外的、标记、当前用户)。configure_scope 接受一个回调函数,并将当前的作用域传递给它。
使用这种基于回调的 API 的原因是效率。如果禁用了 SDK,它就不应该调用回调函数,从而避免不必要的工作。
Sentry.configureScope(scope =>
scope.setExtra("character_name", "Mighty Fighter"));
scope.set_user(user):浅合并用户Shallow merges配置(电子邮件email,用户名username等)。删除用户数据是 SDK 定义的,可以使用remove_user函数,也可以不传递任何数据。scope.set_extra(key, value):将附加键设置为任意值,覆盖潜在的先前值。 删除key是SDK定义的,可以使用remove_extra函数或不传递任何数据作为数据。这是不推荐使用的功能,应鼓励用户改用上下文。scope.set_extras(extras):设置一个具有key/value对,便捷功能的对象,而不是多个set_extra调用。与set_extra一样,这被视为已弃用的功能。scope.set_tag(key, value):将tag设置为字符串值,覆盖潜在的先前值。 删除key是SDK定义的,可以使用remove_tag函数或不传递任何数据作为数据。scope.set_tags(tags):设置一个具有key/value对,便捷功能的对象,而不是多个set_tag调用。scope.set_context(key, value):将上下文键设置为一个值,覆盖一个潜在的先前值。删除key是SDK定义的,可以使用remove_context函数或不传递任何数据作为数据。 这些类型是sdk指定的。scope.set_level(level):设置在此scope内发送的所有事件的级别。scope.set_transaction(transaction_name):设置当前transaction的名称。scope.set_fingerprint(fingerprint[]):将指纹设置为将特定事件分组在一起。scope.add_event_processor(processor):注册事件处理器函数event processor。它接受一个事件并返回一个新事件,或者返回None来将其删除。这是许多集成的基础。scope.add_error_processor(processor)(optional):注册错误处理器函数。 它接受一个事件和异常对象,并返回一个新事件或“None”将其删除。 这可用于从SDK无法提取自身的异常对象中提取其他信息。scope.clear():将scope重置为默认值,同时保留所有已注册的事件处理器event processors。这不会影响子作用域或父作用域。scope.add_breadcrumb(breadcrumb):将面包屑添加到当前scope。scope.clear_breadcrumbs():从scope中删除当前的面包屑breadcrumbs。scope.apply_to_event(event[, max_breadcrumbs]):将scope数据应用于给定的事件对象。这也适用于内部存储在scope中的事件处理器event processors。 一些实现可能想要在此处设置最大面包屑计数。
Client
Client 是 SDK 中负责事件创建的部分。 例如,Client 应将异常转换为 Sentry event。Client 应该是无状态的,它会注入作用域并委托将事件发送到 Transport 的工作。
Client::from_config(config):(或者是普通的构造函数)这通常采用带有options + dsn的对象。Client::capture_event(event, scope):通过将事件与其他数据(client默认设置)合并来捕获事件。另外,如果将scope传递到此系统,则来自该范围的数据会将其传递到内部transport。Client::close(timeout):刷新队列直到超时秒。如果客户端能够保证事件的交付仅持续到当前时间点,则首选此方法。这可能会因为超时秒而阻塞。在调用close后,客户端应该被禁用或销毁。Client::flush(timeout):和close的区别一样,客户端在调用flush后不会被释放。
Hints
(可选)支持事件捕获和面包屑添加的附加参数:hint。
hint 是特定于 SDK 的,但提供了关于事件起源的高级信息。例如,如果捕获了一个异常,提示可能携带原始异常对象。并不是所有的 SDK 都需要提供这个功能。然而,这个参数是为此目的保留的。
Event Pipeline
capture_event 捕获的事件将按以下顺序处理。
注意:事件可以在任何阶段丢弃,此时不会发生进一步的处理。
- 如果禁用
SDK,Sentry会立即丢弃该事件。 - 客户端根据配置的采样速率对事件进行采样。事件可以根据抽样率随机丢弃。
- 使用
apply_to_event应用该作用域。按顺序调用作用域的事件处理器。 Sentry调用before-send钩子。Sentry将事件传递到配置的transport。如果传输没有有效的DSN,则可以丢弃该事件;它的内部队列已满;或由于服务器要求的速率限制。
Options
许多选项都是跨 SDK 标准化的。有关这些选项的列表,请参阅 the main options documentation。
我是为少
微信:uuhells123
公众号:黑客下午茶
加我微信(互相学习交流),关注公众号(获取更多学习资料~)

浙公网安备 33010602011771号