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

SDK 开发

  1. 顶级开源项目 Sentry 20.x JS-SDK 设计艺术(理念与设计原则篇)
  2. 顶级开源项目 Sentry 20.x JS-SDK 设计艺术(开发基础篇)
  3. 顶级开源项目 Sentry 20.x JS-SDK 设计艺术(概述篇)

系列

  1. Snuba:Sentry 新的搜索基础设施(基于 ClickHouse 之上)
  2. Sentry 10 K8S 云原生架构探索,Vue App 1 分钟快速接入
  3. Sentry(v20.x)玩转前/后端监控与事件日志大数据分析,使用 Helm 部署到 K8S 集群
  4. Sentry(v20.x) JavaScript SDK 三种安装加载方式
  5. Sentry(v20.x) JavaScript SDK 配置详解
  6. Sentry(v20.x) JavaScript SDK 手动捕获事件基本用法
  7. Sentry(v20.x) JavaScript SDK Source Maps详解
  8. Sentry(v20.x) JavaScript SDK 故障排除
  9. Sentry(v20.x) JavaScript SDK 1分钟上手性能监控
  10. Sentry(v20.x) JavaScript SDK 性能监控之管理 Transactions
  11. Sentry(v20.x) JavaScript SDK 性能监控之采样 Transactions
  12. Sentry(v20.x) JavaScript SDK Enriching Events(丰富事件信息)
  13. 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 时,我们可以添加一些新的功能,而不是单纯的事件报告(transactionsAPM等)。
  • 设计具有相同 client 实例的 SDK,我们既可以通过依赖项注入等在运行时环境中自然工作,也可以使用隐式上下文分派给已经存在的 clientsscopes,以挂接到大多数环境中。 这很重要,因为它允许事件将流程中其他集成的数据包括在内。
  • 常见任务必须简单明了。
  • 为了帮助第三方库,“non configured Sentry” 的情况需要快速处理(和延迟执行)。
  • 通用 API 需求在大多数语言中都是有意义的,并且一定不能依赖于超级特殊的构造。 为了使它更自然,我们应该考虑语言细节,并明确地支持它们作为替代方法(disposables, stack guards 等)。

简化过的图解

术语

  • minimal:一个单独的 “facade” 包,它通过接口(interfaces)或代理(proxies)重新导出 SDK 功能的子集。该包不直接依赖于 SDK,相反,如果没有安装 SDK,它应该使每个操作都成为 noop。这样一个包的目的是允许 random 库记录面包屑和设置上下文数据,同时不依赖 SDK
  • hub:管理状态的对象。默认情况下可以使用隐含的 global thread local 或类似的 hubHubs 可以手动创建。
  • scopescope 包含了应该与 Sentry 事件一起隐式发送的数据。它可以保存上下文数据、额外参数、级别覆盖、指纹等。
  • clientclient 是只配置一次的对象,可以绑定到 hub。然后,用户可以自动发现 client 并分派对它的调用。用户通常不需要直接与 client 打交道。它们要么通过 hub 实现,要么通过 static convenience functions 实现。client 主要负责构建 Sentry 事件并将其发送到 transport
  • client options:是特定于语言和运行时的参数,用于配置 client。 这可以是 releaseenvironment,也可以是要配置的 integrationsin-app works 等。
  • contextContextsSentry 提供额外的数据。有特殊的上下文( user 和类似的)和通用的上下文(runtime,os,device),等等。检查有效键的 Contexts。注意:在旧的 SDK 中,您可能会遇到一个与上下文无关的概念,这个概念现在已被作用域弃用。
  • tagsTags 可以是任意 string → 可以搜索事件的 string pairsContexts 被转换为 tags
  • extraclient users 附加的真正任意数据。这是一个已弃用的特性,但在可预见的未来将继续得到支持。鼓励用户使用上下文代替。
  • transporttransport 是对事件发送进行抽象的客户端的内部构造。通常,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 认为 clientactive 的。否则,客户端是 inactive 的,SDK 被认为是 “disabled”。在这种情况下,某些回调函数,例如 configure_scope 或事件处理器(event processors),可能不会被调用。因此,面包屑(breadcrumbs)不会被记录下来。

"Static(静态)API"

静态 API 函数是最常见的面向用户的 API。用户只需导入这些功能,即可开始向 Sentry 发出事件或配置作用域。这些快捷方式功能应在包的顶级名称空间中导出。 他们在后台使用 hubsscopes(有关更多信息,请参见并发性 Concurrency)(如果在该平台上可用)。请注意,下面列出的所有函数大部分都是 Hub::get_current().function 的别名。

  • init(options):这是每个 SDK 的入口点。

通常,这会创建(creates)/重新初始化(reinitializes)传播到所有新线程(new threads)/执行上下文(execution contexts)的global hub,或者为每个线程(per thread)/执行上下文(execution context)创建一个 hub

接受 optionsdsn 等),配置 client 并将其绑定到当前 hub 或对其进行初始化。应返回一个 stand-in,可用于 drain events(一次性)。

这可能会返回一个 handleguard 来处理。如何实现这一点完全取决于 SDK。这甚至可能是一个 client,如果这对 SDK 有意义的话。在 Rust 中,它是一个 ClientInitGuard,在 JavaScript 中,它可以是一个带有可等待的 close 方法的 helper 对象。

您应该能够多次调用此方法,而第二次调用它既可以拆除先前的 client,也可以减少先前 client 的引用计数,等等。

多次调用只能用于测试。如果您在应用程序启动以外的任何时间调用 init,将会是 undefined

用户必须调用一次 init,但允许使用禁用的 DSN 进行调用。
例如可能没有参数传递等。

此外,它还设置了所有默认的集成。

  • capture_event(event):接受一个已经组合好的事件,并将其调度到当前活动的中心。 事件对象可以是普通字典或类型化的对象,无论在SDK中更有意义。 它应尽可能遵循本机协议,而忽略平台特定的重命名(案例样式等)。
  • capture_exception(error):报告 errorexception 对象。根据平台的不同,可能有不同的参数。最明显的版本只接受一个 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 将用作新生成的线程的基础,该线程将获得基于主 hubhub(但又是独立的)。
  • Internally scoped hub:在一些平台上,如 .NET ambient data 是可用的,在这种情况下 Hub 可以内部管理作用域scopes
  • Dummy hub:在一些平台上,并发性concurrency本身并不存在。在这种情况下,hub 可能完全不存在,或者只是一个没有并发管理concurrency management的单例。

Hub

在正常情况下,hub 由一堆 clientsscopes 组成。

SDK 维护两个变量:main hub(一个全局变量)和 current hub(当前线程thead或执行上下文execution context的本地变量,有时也称为异步本地async local或上下文本地context local变量)

  • Hub::new(client, scope):使用给定的 clientscope 创建一个新的 hubclient 可以在 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 / exceptioncapture eventcapture_event 将传递的 eventscope 数据合并,并分派给 client。作为附加参数,它还需要一个提示。有关 hint 参数,请参见 hints
  • Hub::push_scope():推送一个继承前一个数据的新作用域层new scope layer。 这应返回有意义的语言的 disposablestack 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):返回当前 clientNoneAccessorgetter
  • 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):将附加键设置为任意值,覆盖潜在的先前值。 删除 keySDK 定义的,可以使用 remove_extra 函数或不传递任何数据作为数据。这是不推荐使用的功能,应鼓励用户改用上下文。
  • scope.set_extras(extras):设置一个具有 key/value 对,便捷功能的对象,而不是多个 set_extra 调用。与 set_extra 一样,这被视为已弃用的功能。
  • scope.set_tag(key, value):将 tag 设置为字符串值,覆盖潜在的先前值。 删除 keySDK 定义的,可以使用 remove_tag 函数或不传递任何数据作为数据。
  • scope.set_tags(tags):设置一个具有 key/value 对,便捷功能的对象,而不是多个 set_tag 调用。
  • scope.set_context(key, value):将上下文键设置为一个值,覆盖一个潜在的先前值。删除 keySDK 定义的,可以使用 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

ClientSDK 中负责事件创建的部分。 例如,Client 应将异常转换为 Sentry eventClient 应该是无状态的,它会注入作用域并委托将事件发送到 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 捕获的事件将按以下顺序处理。

注意:事件可以在任何阶段丢弃,此时不会发生进一步的处理。

  1. 如果禁用 SDK, Sentry 会立即丢弃该事件。
  2. 客户端根据配置的采样速率对事件进行采样。事件可以根据抽样率随机丢弃。
  3. 使用 apply_to_event 应用该作用域。按顺序调用作用域的事件处理器。
  4. Sentry 调用 before-send 钩子。
  5. Sentry 将事件传递到配置的 transport。如果传输没有有效的 DSN,则可以丢弃该事件;它的内部队列已满;或由于服务器要求的速率限制。

Options

许多选项都是跨 SDK 标准化的。有关这些选项的列表,请参阅 the main options documentation

我是为少
微信:uuhells123
公众号:黑客下午茶
加我微信(互相学习交流),关注公众号(获取更多学习资料~)
posted @ 2021-03-13 12:14  为少  阅读(632)  评论(0编辑  收藏  举报