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
公众号:黑客下午茶
加我微信(互相学习交流),关注公众号(获取更多学习资料~)