排查问题

虽然我们不期望大多数用户会遇到这些问题,但我们在此记录了这些边缘情况。

我们期望大多数 Python SDK 用户不会遇到这里记录的任何问题。

使用本页面的信息来帮助解答以下问题:

  • “如果请求之间出现作用域数据泄漏怎么办?”
  • “如果事务中的跨度应该是并行的但却嵌套了怎么办?”
  • “如果 SDK 在向 Sentry 发送事件时遇到问题怎么办?”

前两个问题的简短答案:确保您的 contextvars 正常工作,并且为每个并发单元克隆了隔离作用域。

Python 支持几种不同的并发解决方案,包括线程和协程。

Python SDK 尽力确保上下文数据(例如使用 sentry_sdk.set_tags 设置的标签)能够正确地沿控制流传递。在大多数情况下,它能完美工作,但在某些情况下需要特别注意。特别是在处理不在提供的框架集成范围内的并发代码库时。

一般建议是为每个“并发单元”(线程/协程等)设置一个隔离作用域。SDK 通过 ThreadingIntegration 确保每个线程都有一个独立的作用域。如果您使用 asyncio 协程进行并发处理,请确保使用 AsyncioIntegration,它会在您的 Task 中克隆正确的上下文。

创建新隔离作用域的一般使用模式是:

Copied
with sentry_sdk.isolation_scope() as scope:
    # In this block scope refers to a new fork of the original isolation scope,
    # with the same client and the same initial scope data.

参见 线程 部分,了解涉及 fork 隔离作用域的更完整示例。

Python SDK 使用 线程局部变量 来保持上下文数据在其应有的位置。在某些情况下,这种方法可能会失败。

如果您无法理解为什么上下文数据会在 HTTP 请求之间泄漏,或者数据缺失或出现在错误的地方和时间,请继续阅读。

如果 SDK 安装在 Python 2 上,除了上述的线程局部变量外没有太多其他选择,因此 SDK 将仅使用线程局部变量。

使用异步库(如 twisted)的代码不受支持,这意味着您会遇到上下文数据在任务或任何逻辑边界之间泄漏的问题,至少在默认情况下是这样的。

使用更“魔法”的异步库(如 **geventeventlet)的代码只要这些库配置为 monkeypatch 标准库,就可以正常工作。例如,如果您仅在运行 gunicorn 的上下文中使用这些库,情况就是这样。

Python 3 引入了 asyncio,它与 Twisted 一样,没有附带将上下文数据附加到控制流的概念。这意味着在 Python 3.6 及以下版本中,SDK 无法防止上下文数据泄漏。

Python 3.7 通过 contextvars 标准库模块解决了这个问题,该模块基本上是也可以在基于 asyncio 的代码中工作的线程局部变量。如果可用,SDK 将尝试使用该模块而不是线程局部变量。

对于 Python 3.6 及更早版本,请从 PyPI 安装 aiocontextvars,这是 contextvars 的一个功能完整的回溯移植。SDK 会检查此包并使用它来替代线程局部变量。

如果您在应用程序中使用了 gevent(版本低于 20.5)或 eventlet 并配置为 monkeypatch 标准库,即使 contextvars 可用,SDK 也不会使用 contextvars

原因是这些库只会 monkeypatch threading 模块,而不会 monkeypatch contextvars 模块。

一个实际场景是在 Python 3.7 中使用 gunicorn+gevent 工作者运行 Django 3.0。在这种情况下,monkeypatched 的 threading 模块将遵循 gunicorn 工作者的控制流,而未修补的 contextvars 则不会。

如果在同一应用程序中使用 Django Channels 和单独的服务器进程,情况会更加复杂,因为这是 asyncio 的合法使用场景,contextvars 在这种情况下表现得更正确。确保您的 Channels WebSocket 服务器完全不导入或使用 gevent(更不要调用 gevent.monkey.patch_all),这样应该可以正常工作。

即使如此,仍然存在一些边缘情况,这种行为会完全失效,例如混合使用 asyncio 代码和基于 gevent/eventlet 的代码。在这种情况下,没有静态的正确答案来决定使用哪个上下文库。即使如此,gevent 的激进 monkeypatching 很可能会以无法从 SDK 内部修复的方式干扰。

这个问题在 gevent 20.5 中已被修复,但对于 eventlet 仍然存在。

您的 SDK 可能在发送事件到 Sentry 时遇到问题。您可能会在日志中看到类似 "Remote end closed connection without response""Connection aborted""Connection reset by peer" 或类似的错误消息。

对于错误和跟踪数据,这会表现为 Sentry 中缺少错误和事务。对于 cron 监控,您可能会看到在 Sentry 中被标记为超时的 cron 作业,而实际上它们已经成功运行。SDK 本身可能会记录关于连接重置或服务器在没有响应的情况下关闭连接的错误。

如果您遇到这种情况,尝试启用 keep-alive 配置选项,该选项在 SDK 版本 1.43.0 及以上版本中可用。

Copied
import sentry_sdk

sentry_sdk.init(
    # your usual options
    keep_alive=True,
)

如果您需要对套接字行为进行更精细的控制,请参阅 套接字选项

如果您使用的是 Python 3.12 或更高版本,您可能会在 Linux 环境中看到以下弃用警告,因为 SDK 会启动多个线程。

Copied
DeprecationWarning: This process is multi-threaded, use of fork() may lead to deadlocks in the child.

要移除此弃用警告,请将 多进程启动方法设置为 spawnforkserver。 请确保仅在 __main__ 块中进行此设置。

Copied
import sentry_sdk
import multiprocessing
import concurrent.futures

sentry_sdk.init()

if __name__ == "__main__":
    multiprocessing.set_start_method("spawn")
    pool = concurrent.futures.ProcessPoolExecutor()
    pool.submit(sentry_sdk.capture_message, "world")