Polar 后端 Sentry 问题全链路排查与修复实战:从 correlation_id 关联 Logfire 到 Worktree 提交 PR
【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar
本文基于 Polar 开源仓库的
.agents/skills/fix-sentry/SKILL.md技能文档,完整还原 Polar 团队在日常开发中排查与修复 Sentry 上报问题的标准工作流:先用 sentry_polar 工具定位错误与堆栈,再以correlation_id为纽带关联 Logfire 全链路日志,随后回到源码搜索根因、形成假设,最后在独立 git worktree 中实现修复并通过 GitHub 工具提交 PR。读完本文,你将掌握一套可复用的“可观测性数据交叉验证 + 源码定位 + 隔离开发”的线上问题修复方法论。
技能定位:谁在什么场景下使用 fix-sentry
在 Polar 仓库中,fix-sentry是一个面向 AI Agent 的 Claude Code 技能(Skill),其元信息定义在 .agents/skills/fix-sentry/SKILL.md 的文件头 Frontmatter 中:
--- name: fix-sentry description: Analyze and fix issues reported by Sentry in the Polar codebase. user-invocable: true allowed-tools: Bash(gh:*) Bash(git:*) logfire_polar* sentry_polar* github* ---- description:明确技能职责是“分析并修复 Polar 代码库中由 Sentry 上报的问题”;
- user-invocable: true:用户可直接显式调用该技能;
- allowed-tools:技能运行期间被允许使用的工具集合,包括 GitHub CLI(
gh:*)、Git 命令(git:*)、Logfire 相关工具(logfire_polar*)、Sentry 相关工具(sentry_polar*)以及 GitHub 工具(github*)。这说明整个流程依赖四类能力:Sentry 数据访问、Logfire 日志检索、源码检索、GitHub 协作。
技能文档对执行者(Agent)的要求是:调查 Sentry 报告、识别问题根因、并实施修复以解决问题。整个流程被拆解为 5 个明确步骤,下面逐一展开。
第一步:确定输入 —— 获取 Sentry Issue ID
技能在执行前需要拿到一个 Sentry Issue ID 或 Sentry Issue 链接。如果调用提示(invocation prompt)中没有提供,应当主动向用户询问,以便访问问题详情并开始分析。
Polar 在 Sentry 中的组织标识是两个固定值:
- Organization ID:
4505046560538624 - Slug:
polar-sh
这两个标识在技能的所有sentry_polar工具调用中作为组织维度参数使用。
第二步:分析 Sentry Issue(Step 1)
使用sentry_polar工具访问用户提供的 Sentry Issue 详情。技能要求获取的信息包括:
- 错误消息(error message)
- 堆栈追踪(stack trace)
- 与该问题关联的附加上下文或元数据(additional context / metadata)
Sentry 报告往往只是“症状”,包含发生位置与调用栈,但缺少业务链路上下文。因此下一步必须借助日志系统还原“来龙去脉”。
第三步:关联 Logfire 日志(Step 2)—— correlation_id 的妙用
技能要求从 Sentry Issue 中提取correlation_id标签(tag),若存在则用它到 Logfire 中检索关联日志,从而获得问题发生前的事件上下文,帮助理解根因。
检索子句形式如下:
attributes->>'correlation_id' = '<correlation_id>'注意:如果检索到的日志中带有
source_correlation_id,也需要一并查询这些日志。它代表“来源方”的关联 ID(典型场景是后台任务由某次 HTTP 请求触发时,任务日志会保留请求的source_correlation_id)。
correlation_id 从何而来:中间件与后台任务
correlation_id并不是 Sentry 或 Logfire 自动生成的,而是 Polar 在请求入口主动注入的。核心实现在 server/polar/middlewares.py 的LogCorrelationIdMiddleware:
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] != "http": return await self.app(scope, receive, send) correlation_id = CorrelationID.set() structlog.contextvars.bind_contextvars( correlation_id=correlation_id, method=scope["method"], path=scope["path"] ) sentry_sdk.set_tag("correlation_id", correlation_id) ...该中间件在每次 HTTP 请求进入时:
- 调用
CorrelationID.set()生成一个新的 UUID(str(uuid.uuid4())),CorrelationID类定义在 server/polar/logging.py,内部基于contextvars.ContextVar("polar.correlation_id")实现请求级线程上下文隔离; - 通过
sentry_sdk.set_tag("correlation_id", correlation_id)写入 Sentry 标签——这就是 Sentry Issue 上correlation_id标签的来源; - 通过
logfire.set_baggage(correlation_id=correlation_id)写入 OpenTelemetry baggage,并直接为 OTel 根 Span 设置correlation_id属性(注释说明:根 Span 由更早执行的 OTel ASGI 中间件创建,baggage 不会被自动拾取,因此必须显式root_span.set_attribute("correlation_id", correlation_id)); - 同时绑定 structlog 上下文,使应用日志也携带
correlation_id、method、path。
该中间件还会捕获移动端 App 发送的x-polar-client-version、x-polar-client-runtime、x-polar-client-update三个请求头,作为client_version、client_runtime、client_update标签同步写入 structlog、Sentry 与根 Span,用于按客户端构建版本追溯兼容性问题。请求结束后在finally中统一清理上下文。
后台任务侧同样会生成correlation_id。在 server/polar/worker/_runner.py 的任务执行器_run_async中:
correlation_id = CorrelationID.set() structlog.contextvars.bind_contextvars( actor_name=actor_name, correlation_id=correlation_id, source_correlation_id=source_correlation_id, ) ... with _task_span(actor_name, message, correlation_id, source_correlation_id): ...任务执行时会生成新的correlation_id,同时把触发来源的source_correlation_id(若任务由请求派发而来)一并绑定,并在 Logfire 的_task_span中同时记录这两个 ID。这正是技能要求“同时查询source_correlation_id日志”的原因——它可以帮你从后台任务回溯到发起它的那次 HTTP 请求。
Logfire 侧的检索依据
correlation_id之所以能在 Logfire 中被 SQL 检索,是因为 server/polar/logfire.py 中configure_logfire完成了 OpenTelemetry 导出配置。该模块值得注意的细节包括:
- 采样控制:通过自定义
IgnoreSampler过滤掉/healthz健康检查与 worker 心跳类 Span(_healthz_matcher、_worker_health_matcher),再叠加LevelSampler按settings.LOG_LEVEL过滤低级别日志; - 脱敏(scrubbing):
_scrubbing_callback明确豁免subject、thread_stacks、event_loop_stack、asyncio_tasks等路径,防止误伤认证主体与事件循环栈中的"session"字样; - S3 导出:当配置了
S3_LOGS_BUCKET_NAME时,会通过S3SpanExporter以 2048 条/批、60 秒延迟的节奏把 Span 批量导出到 S3,并提供email、user.name、phone、address、ip_address、cookie、http.url等模式的脱敏规则。
第四步:搜索源码(Step 3)
利用从 Sentry Issue 与 Logfire 日志中获得的信息,回到代码库中检索相关代码。技能建议的检索维度包括:
- 具体的错误消息文本(error messages)
- 函数名(function names)
- 其他相关关键词(relevant keywords)
在 Polar 仓库中,可优先在 server/polar 目录下按业务模块检索(如checkout/、subscription/、benefit/、webhook/等),或直接搜索唯一性强的标识符。由于 structlog 日志、Sentry 标签与 Logfire Span 都携带correlation_id,你甚至可以先用correlation_id本身在代码库中检索,定位中间件、日志绑定与任务执行等关键链路。
第五步:分析发现,形成根因假设(Step 4)
综合 Sentry Issue、Logfire 日志与源码检索三方面的信息,综合归纳(synthesize)后对问题根因形成一个假设(hypothesis)。
一个典型的排查闭环示例:
- Sentry 报告某接口抛错并给出堆栈,附带
correlation_id标签; - 用
attributes->>'correlation_id' = '<correlation_id>'在 Logfire 中拉出该请求的完整 Span 序列与前后日志,发现数据库查询超时或某外部 API 调用失败; - 回到源码检索堆栈中的函数名,确认失败点所在模块与调用参数;
- 最终假设:例如“某个枚举值未处理导致状态机走到非法分支”或“未对空列表做防御性判空”。
假设形成后,进入修复阶段。
第六步:实施修复(Step 5)
技能在此处设置了一条重要边界:
重要:如果分析表明问题涉及重度架构变更(heavy architectural changes),或者你不确定最佳修复方案,不要自行实施修复。此时应在
polarsource/polar上开一个 Issue,附上你的发现与分析结果。
只有确认是局部、可控的修复时,才走下述 4 个子步骤。
Step 5.1:创建独立 Worktree(强制)
始终先为要开展的工作创建 git worktree,使改动与主代码库隔离,直到准备合并。运行:
./dev/create-worktree <branch-name><branch-name>必须是仅由小写字母、数字和下划线组成的描述性名称(脚本强制校验^[a-z0-9_]+$,不合法会直接报错退出);- Worktree 创建在仓库的
.worktrees目录下(技能文档写作./worktrees,以脚本实际实现 dev/create-worktree 为准),之后用以下命令进入:
cd .worktrees/<branch-name>从脚本实现看,dev/create-worktree实际做了三件事:
git worktree add .worktrees/<name>创建独立工作目录;- 在本地 PostgreSQL 中为本次开发创建独立数据库
polar_dev_<name>(若不存在):PGPASSWORD=$password psql -h "$host" -U "$user" -d "postgres" -tAc \ "SELECT 1 FROM pg_database WHERE datname='$db_name'" | grep -q 1 - 在新 worktree 中运行
./dev/cli/dev up --skip-integrations --database-name "$db_name",完成带独立数据库的环境初始化(--skip-integrations跳过集成依赖,--database-name指向新库)。
IMPORTANT:从此刻起,所有修改与命令都必须在刚创建的 worktree 上下文内执行,以保证改动隔离、易于管理。
Step 5.2:实现修复
基于分析结论实施修复,可能涉及:
- 修改现有代码
- 新增代码
- 配置变更
提交前必须按仓库 AGENTS.md 的约定执行检查,即运行lint(代码检查)、类型检查(type checking)与测试(tests)。
Step 5.3:提交变更
把改动提交到 worktree 中创建的本地分支。提交信息要求清晰且有描述性,既要说明改了什么,也要说明为什么这么改(reason for those changes)。
Step 5.4:打开 Pull Request
使用github工具在polarsource/polar上打开 PR,PR 描述中需要:
- 详述分析过程(your analysis);
- 附上 Sentry Issue 与 Logfire 日志的相关链接;
- 说明为修复问题所做的改动。
这样既为 reviewer 提供了完整证据链,也便于后续回溯。
与可观测性基础设施的联动:Sentry 侧的代码佐证
技能流程中的sentry_polar工具所读到的 Issue 数据,最终都来自 server/polar/sentry.py 的configure_sentry初始化逻辑。理解其配置有助于判断哪些 Issue 值得跟进:
def configure_sentry(*, aws_lambda: bool = False) -> None: sentry_sdk.init( dsn=settings.SENTRY_DSN, traces_sample_rate=None, # `0` 仍会参与 trace continuation profiles_sample_rate=None, release=os.environ.get("RELEASE_VERSION", "development"), server_name=os.environ.get("RENDER_INSTANCE_ID", "localhost"), environment=settings.ENV, default_integrations=False, auto_enabling_integrations=False, before_send=before_send, integrations=[...], )几个直接影响排查体验的关键点:
before_send过滤:若事件 tags 中is_operational_error为"true",事件会被直接丢弃(返回None)。这意味着你在 Sentry 上看到的 Issue 已经排除了被标记为“操作性错误”的事件,聚焦真正的代码缺陷;- Integration 组合:同时启用
StarletteIntegration与FastApiIntegration(Sentry 官方 FastAPI 文档明确要求两者都装),transaction_style="endpoint"让事务名以接口端点命名;DramatiqIntegration为 Polar 自定义子类,用于在 broker 已初始化后才注入SentryMiddleware;AWS Lambda 环境会追加AwsLambdaIntegration; - 用户身份关联:
set_sentry_user在已登录用户请求中写入sentry_sdk.set_user({"id": ..., "email": ...}),并额外设置posthog_distinct_id标签(server/polar/sentry.py),便于把错误关联到具体用户会话; - 日志面包屑:
LoggingIntegration(level=logging.INFO, event_level=None)会把 INFO 及以上日志作为 breadcrumbs 附加到事件,配合correlation_id标签,可以在 Sentry Issue 详情页直接看到请求链路关键日志。
完整流程速查
| 步骤 | 动作 | 关键工具/命令 | 产物 |
|---|---|---|---|
| 输入 | 获取 Sentry Issue ID 或链接 | 询问用户;org id4505046560538624/ slugpolar-sh | Issue 标识 |
| Step 1 | 分析 Sentry Issue | sentry_polar | 错误消息、堆栈、上下文 |
| Step 2 | 关联 Logfire 日志 | logfire_polar,attributes->>'correlation_id' = '<correlation_id>' | 请求/任务链路上下文 |
| Step 3 | 搜索源码 | 源码检索(错误文本/函数名/关键词) | 相关代码片段 |
| Step 4 | 分析发现 | 综合三路信息 | 根因假设 |
| Step 5.1 | 创建 worktree | ./dev/create-worktree <branch-name> | .worktrees/<branch-name>隔离环境 |
| Step 5.2 | 实现修复 | 修改代码 + lint/类型/测试(见 AGENTS.md) | 修复代码 |
| Step 5.3 | 提交变更 | git commit | 描述性提交 |
| Step 5.4 | 打开 PR | github工具 | 含分析、Sentry/Logfire 链接的 PR |
注意事项与最佳实践小结
- 重大变更先开 Issue:涉及重度架构调整或对修复方案不确定时,绝不盲目动手,应先在
polarsource/polar开 Issue 并附上分析; - 隔离开发:一切改动在 worktree 中完成,主代码库保持干净,便于多任务并行与安全合并;
- 证据链完整:PR 中必须附带 Sentry Issue 链接、Logfire 日志链接与分析过程,这是 Polar 团队可观测性工作流的核心纪律;
- 双向追溯:HTTP 请求通过
LogCorrelationIdMiddleware生成correlation_id并注入 Sentry/Logfire/structlog;后台任务在 server/polar/worker/_runner.py 中生成新correlation_id并保留source_correlation_id。排查时两条链路都要查,才能还原完整事件因果。
【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考