captureException(以及同类方法)会排队事件。HTTP POST 是异步的。process.exit / os.Exit / Lambda 冻结会在那个 POST 完成之前杀死进程。仪表盘保持空白。await Sentry.flush() 或 await Sentry.close();在 Go 中 sentry.Flush(2 * time.Second),并且要在你退出之前调用。你部署了一个 cron、一个 Lambda、一个 CLI,或一个一次性 k8s Job。有东西抛异常了。你调用 captureException。进程退出了。你刷新错误仪表盘。什么都没有。你重新检查 DSN,怪罪"Sentry 不稳定",然后在一个从头到尾都正常的网络路径上烧掉整个下午。
SDK 做了你要求的事:它把事件加入了队列。但进程在传输完成之前就死了。这篇文章是 Node(@sentry/node)和 Go(getsentry/sentry-go)通用的 flush 配方。同样的调用对 SaaS Sentry、GlitchTip 或任何支持 Sentry 协议的自托管采集服务都有效。
采集是异步的。退出会结束进程。SDK 队列里任何还在的东西永远不会离开这台机器。
官方 SDK 不会在每次 captureException 时阻塞你的处理器。它们把事件序列化,交给内部的 transport,然后返回。这对长期运行的服务器是正确的:你不会想让每个错误都卡住一次请求。
短生命周期的工作进程有不同的生命周期。任务在几百毫秒内就结束了。你调用 process.exit(1),或者运行时冻结了 Lambda。事件缓冲区还有活儿。不再有事件循环 tick。不再有 goroutine。POST 永远不会发生。
心智模型:capture = "请尽快发送这个。"flush/close = "阻塞直到队列清空(或超时),然后我才允许退出。"
用 captureException 加上立即 process.exit、并且不 flush 来复现。即使 DSN 有效,也预期会得到一个空的问题流。
锁定你实际会运行的 SDK 版本:
npm i @sentry/node@11
broken-worker.mjs:
import * as Sentry from "@sentry/node";
Sentry.init({
dsn: process.env.SENTRY_DSN, // 任何有效的 Sentry 协议 DSN
tracesSampleRate: 0,
});
async function main() {
try {
throw new Error("flush-demo: cron blew up");
} catch (err) {
Sentry.captureException(err);
// 看起来完成了……其实没有
process.exit(1);
}
}
main();
运行它:
SENTRY_DSN='https://PUBLIC@HOST/PROJECT' node broken-worker.mjs
有 bug 时的预期表现
1 退出。flush-demo: cron blew up 没有任何新 issue(或者出现得很晚 / 永不出现,取决于竞态)。可选的自检:暂时在 Sentry.init 里设置 debug: true。你常常会看到 SDK 在进程已经死了之后还在准备或发送。
如果进程可能继续运行,就 await Sentry.flush(timeoutMs);如果在退出之前,就用 Sentry.close(timeoutMs)。两者都返回 Promise<boolean>:如果队列及时排空则为 true。
fixed-worker.mjs:
import * as Sentry from "@sentry/node";
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0,
});
async function main() {
try {
throw new Error("flush-demo: cron blew up");
} catch (err) {
Sentry.captureException(err);
// 排空队列,然后禁用客户端(退出前安全)
const ok = await Sentry.close(2000);
if (!ok) {
console.warn("Sentry.close timed out; some events may be lost");
}
process.exit(1);
}
}
main();
如果你只需要排空队列并且进程会继续(一个进程里跑一批任务),优先用 flush:
const ok = await Sentry.flush(2000);
// 客户端保持启用
SIGTERM / 优雅关闭(Compose、k8s、systemd):
async function shutdown(signal) {
console.log(`got ${signal}, flushing Sentry`);
await Sentry.close(2000);
process.exit(0);
}
process.on("SIGTERM", () => {
shutdown("SIGTERM");
});
process.on("SIGINT", () => {
shutdown("SIGINT");
});
本文使用的锁定版本
包 | 锁定版本 |
|---|---|
@sentry/node | 11(对照 11.0.0 文档 API 验证过形态) |
修复后的预期表现
close / flush 最多耗时你的超时时间(这里 2 秒),在网络路径正常时解析为 true。flush-demo: cron blew up 的 issue 会在几秒内出现在项目里。lastEventId()(可选)在采集后返回一个 id;成功 flush 后你可以在 UI 里关联它。AWS Lambda 提示:运行时可以在你的处理器返回后冻结。最好在处理器内部、返回之前就 flush,而不是只依赖一个你可能永远看不到的进程级信号。
在程序终止前调用 sentry.Flush(timeout)。正常返回路径用 defer 没问题;os.Exit 会跳过 defer,所以要在 Exit 之前手动 Flush。
go get github.com/getsentry/sentry-go@v0.49.0
package main
import (
"fmt"
"os"
"time"
"github.com/getsentry/sentry-go"
)
func main() {
err := sentry.Init(sentry.ClientOptions{
Dsn: os.Getenv("SENTRY_DSN"),
})
if err != nil {
fmt.Fprintf(os.Stderr, "sentry.Init: %v\n", err)
os.Exit(1)
}
// 覆盖正常返回路径
defer sentry.Flush(2 * time.Second)
sentry.CaptureException(fmt.Errorf("flush-demo: job failed"))
// os.Exit 跳过 defer;先 Flush
if !sentry.Flush(2 * time.Second) {
fmt.Fprintln(os.Stderr, "sentry.Flush timed out; some events may be lost")
}
os.Exit(1)
}
修复后的预期表现:Flush 返回 true,异常出现在项目里。有 bug 时(capture + os.Exit 且没有 Flush),你会遇到和 Node 一样的空仪表盘陷阱。
不要在热循环里的每次 CaptureException 之后都调用 Flush。那会把异步 transport 变成同步刷屏。在边界处 Flush:任务结束、Exit 之前、关闭时。
短生命周期进程需要显式 flush。长期运行的服务器大多不 flush 也没事,但 SIGTERM 处理器仍然受益。
运行时 | 通常需要 flush 吗? | 为什么 |
|---|---|---|
Cron / systemd oneshot | 是 | 脚本一结束进程就退出 |
AWS Lambda / Cloud Functions | 是 | 返回后冻结;缓冲区随冻结一起死掉 |
CLI / go run 任务 | 是 | 用户按 Ctrl-C 或你 os.Exit |
k8s Job / Compose restart: "no" 一次性 | 是 | 和 cron 一样 |
长期运行的 HTTP API | 通常不需要 | 事件循环 / goroutine 继续运行;SIGTERM 时仍要 flush |
如果你的"服务器"实际上是按消息退出的请求级 worker(某些队列消费者),把它当成 cron 处理。
确认方法:强制制造一个已知错误,等待 flush/close,然后在动 DSN 之前先在项目里看到那一条确切的消息。
确认
flush-demo: …),这样你能快速找到它。flush/close/Flush 返回了 true(把它打日志)。失败检查清单
process.exit / os.Exit。经典错误。把退出移到 await close / Flush 之后。os.Exit 会跳过 defer。Go:在 Exit 前显式 Flush;不要只依赖 deferred Flush。2000 ms 只是起点;冷启动 / 远端区域要调大,或者接受丢失并记录 false。true 但 UI 没有事件,意味着认证 / 主机 / 项目不匹配。在本地证明 flush 正常之后再修 DSN(debug transport 日志有帮助)。beforeSend 丢掉了事件。Flush 只排空 SDK 保留的内容。返回 null 的过滤器永远不会送达。close()。在 Node 里,一个悬空的 promise 会再次输给 process.exit 的竞态。上面的 SDK 调用与主机无关:它们把客户端队列排空到你配置的任何 DSN。对 SaaS Sentry、GlitchTip 或另一个 Sentry 协议采集服务,模式一样。
你有没有因为 worker 在 flush 之前就退出了而丢掉一条只在生产环境出现的异常?你追查错误的 DSN 追了多久?
相关阅读(本站推荐): 如何在 Windows 11/10 中关闭屏幕键盘(4 种方法) 如何在 Windows 11/10 中启用或禁用睡眠模式 如何在 Windows 11/10 中打开控制面板(5 种方法)
如果你也在研究后端开发和运维,欢迎参考本站的 IT 教程文章,一起把技术学扎实。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。