一位学生从 Brave 浏览器中进行 KeepCoding 的心理测试时报告了一个问题,但该问题从未成功上传到 Sentry。团队技术支持人员查看了控制面板,却没有发现任何内容,便让他再次测试。同时,针对生产环境的 Playwright 烟雾测试显示所有情况正常:SDK加载,事件被处理,Sentry.flush()返回了true。一切看起来都没有问题。
依据不完整数据做出的产品决策比凭直觉合理性决策更糟,因为它们是基于虚假的数据安全感做出的。
背景:为什么 Brave 的情况特别重要
Brave 是一款内置广告屏蔽功能的浏览器,称为 Shields(保护盾)。它可以在默认情况下过滤广告和追踪器,而无需安装额外的插件。根据 Brave Software 2024年公布的公开数据,该浏览器的月活跃用户已经超过8000万。这一数字之所以重要,是因为用户分布并不均匀:Brave 的用户基础更倾向于技术人员、开发者和新潮用户。
对于面向普通大众的应用,这种偏向可以忽略不计;但对于以开发者为目标群体,例如针对开发者训练营申请者的技术评估应用来说,首批报告错误的用户极有可能来自使用 Brave 的人。
类似的情况也发生在使用 uBlock Origin 的 Chrome 或 Firefox 用户,以及启用了 **严格追踪保护(Strict Tracking Protection)**的 Firefox 配置用户,或通过企业网络使用 Pi-hole DNS阻止机制的用户。这些类别的交集——即启用了某种制止追踪器的积极拦截机制的用户——据相关阻止名单的覆盖研究显示(如 EasyPrivacy 用户超过400万订阅者),很容易占据技术流量的15-20%。
问题:保护盾会屏蔽两项内容,而不仅仅是一项
Sentry 的浏览器 SDK 以两个阶段工作。在第一阶段,浏览器从 Sentry 的 CDN 下载软件开发包的 JavaScript 包,通常位于 js-de.sentry-cdn.com 或 browser.sentry-cdn.com 下。在第二阶段,SDK 初始化后,将捕获到的事件发送到以 *.ingest.sentry.io 为主机的摄取端口(endpoint)。
Brave 的保护盾(Shields)会检查两个列表:一个是浏览器的全局列表,一个是针对具体网站启用的列表。关闭保护盾仅适用于网站域名,不影响全局列表。而全局列表中包括以下一些域名:
*.sentry-cdn.com
*.ingest.sentry.io
*.ingest.de.sentry.io
此问题已在 Brave 社区的公共讨论线程中 “Brave blocks sentry endpoint even with shield down for website” 提及,但在 Sentry 官方文档中并未做出明确警告。实际后果是,使用 Brave 的用户会在控制台看到两个错误:
GET https://browser.sentry-cdn.com/10.53.1/bundle.min.js
net::ERR_BLOCKED_BY_CLIENT
POST https://o4511410375229440.ingest.de.sentry.io/api/4511410426085456/envelope/
net::ERR_BLOCKED_BY_CLIENT
第一个错误会阻止 SDK 初始化。第二个错误,即使通过其他方式加载了 JavaScript 包,也会阻止事件进入控制台。
为什么烟雾测试会产生误导
团队的第一个反应通常是相信端到端测试(E2E Test)。如果用 Playwright 运行的烟雾测试通过了,那集成应该是有效的。
问题在于 Playwright 启动的是没有保护盾的 Chromium。Brave 基于 Chromium 构建,但保护盾是 Brave Software 专有的一层,并没有安装在标准版 Chromium 的二进制文件中。如果测试是通过 Playwright 使用 Firefox,虽然 Firefox 确实带有追踪保护功能,但并未默认启用 Strict 模式。
结果是一个特别难以诊断的测试盲点:任何针对生产环境的自动化测试都将报告 Sentry 正常工作,而实际上只有在用户使用真实浏览器并启用严格追踪设置时才会暴露问题。
为了准确复现这种情况,团队需要在 CI 环境中下载 Brave,配置保护盾,并使用 playwright._impl._browser_type 指向 Brave 的可执行文件来运行测试。这是可行的,但目前尚未有任何项目默认这么做。
解决方案:三层必须同时工作
Sentry 官方建议的绕过广告拦截器的策略是通过隧道(tunnel)。在其排障文档中简要说明。基本思路是浏览器发送事件到与应用在同一域下的端点,服务器再将事件转发至 Sentry。从浏览器的角度来看,Sentry 的所有流量都隐藏在应用自身的来源之下,广告拦截器不会识别此流量。
官方文档涵盖了这个概念,但并未详细描述将其实际应用到生产环境与真实浏览器所需的细节。
第一步:从自己的域提供 SDK JavaScript 包
第一步是下载 Sentry 的 JavaScript 包并将其作为静态资源提供:
curl -L -o app/static/js/vendor/sentry-browser.min.js \
https://browser.sentry-cdn.com/10.53.1/bundle.min.js
在 HTML 模板中,将原来的 CDN 加载脚本替换为本地的包引用:
<script src="/static/js/vendor/sentry-browser.min.js"></script>
<script
src="/static/js/sentry-init.js"
data-dsn="https://...@o....ingest.de.sentry.io/..."
data-environment="production"
data-release="2026.05.18.001"
></script>
这样,JavaScript 包从广告拦截器名单中未收录的来源加载。如果应用已经采取了_内容安全策略_(Content Security Policy,CSP),这个更改还允许政策更严格化:script-src 可以保持为 'self',无需添加 *.sentry-cdn.com 的例外。
第二步:用 Flask 服务器端实现隧道功能
在介绍代码之前,需要明确一个术语细节。Sentry 的 SDK 并非通过传统的 HTTP 请求将 JSON 数据发送。它会将事件包装为一种称为 envelope 的独立格式:第一行是一段包含包元数据的 JSON 表头,包括项目的 dsn,剩下的行是 SDK 希望在这次请求中上传的事件或其他条目(如附件、会话、事务等)。Sentry 的接收端口始终期望接收这种格式。隧道的作用仅限于接收来自浏览器的 envelope 并转发至 Sentry,除了后续将提到的重写步骤。
最简实现方法如下,用 Flask:
在客户端,变更仅需添加一行配置:
window.Sentry.init({
dsn: dsn,
tunnel: "/api/sentry-tunnel",
environment: environment,
release: release,
tracesSampleRate: 0,
sendDefaultPii: false,
});
这样浏览器会将 envelope POST 至与应用同一域下的 /api/sentry-tunnel。广告拦截器只会看到发往自己服务器的流量,不会拦截。
三个文档未覆盖的重要提示
以下是从表面看来似乎已完成集成,但实际上却无法正常工作的关键原因解析。
提示1:SDK配置tunnel:时可能忽略dsn字段
【保持原文详解翻译不变】
本文原文为西班牙语,借助AI翻译。