浏览器遥测代理与 Core Web Vitals
Telemetry JavaScript SDK 使用秘密 API 密钥,属于可信服务器代码。不要将该密钥捆绑到浏览器应用程序中。要收集 Core Web Vitals 或有界前端可靠性事件,请将一个小的白名单有效负载发送到您自己的同源端点,并让该端点将其转发到 Telemetry。
这种设计将凭证保留在服务器端,并为应用程序提供了一个地方来强制同意、来源检查、速率限制、字段类型、有效负载大小和隐私规则。
建筑
browser measurement
-> POST /api/browser-telemetry
-> origin, size, rate, and schema checks
-> server-side telemetry-sh client
-> browser_performance_measured table
代理不是一般的日志端点。仅接受已知事件名称和已知字段。切勿接受客户端提供的表名称或 Telemetry API 密钥。
定义浏览器合约
对于 Core Web Vitals,每个指标一个事件很容易验证:
type BrowserMetric = {
event_name: "browser_performance_measured";
metric_name: "CLS" | "INP" | "LCP";
metric_value: number;
metric_id: string;
route_template: string;
release: string;
navigation_type: string;
};
使用 /projects/:id 等路由模板,而不是 location.href。请勿发送查询字符串、DOM 文本、表单值、cookie、授权数据、包含标识符的引用站点或任意错误消息。随机指标 ID 可以帮助消除重复重传,但它不应该成为跨站点用户标识符。
在浏览器中收集 Web Vitals
web-vitals 包报告当前的 Core Web Vitals。如果您的政策和司法管辖区需要同意,则在同意后发送:
import { onCLS, onINP, onLCP, type Metric } from "web-vitals";
function reportMetric(metric: Metric) {
const body = JSON.stringify({
event_name: "browser_performance_measured",
metric_name: metric.name,
metric_value: metric.value,
metric_id: metric.id,
route_template: routeTemplateFor(location.pathname),
release: window.__APP_RELEASE__,
navigation_type: metric.navigationType,
});
if (navigator.sendBeacon) {
navigator.sendBeacon(
"/api/browser-telemetry",
new Blob([body], { type: "application/json" }),
);
return;
}
void fetch("/api/browser-telemetry", {
method: "POST",
headers: { "content-type": "application/json" },
body,
keepalive: true,
credentials: "same-origin",
});
}
onCLS(reportMetric);
onINP(reportMetric);
onLCP(reportMetric);
sendBeacon 适用于页面终止期间的小型尽力而为有效负载。无法保证交付,浏览器扩展可能会阻止请求,并且用户可能会在传输之前关闭页面。将浏览器测量结果视为采样的体验数据,而不是精确的计费或审计分类帐。
在服务器上验证并转发
端点应拒绝未知来源、事件名称、指标名称、非有限值、超大主体和意外字段。下面的例子显示了边界;使请求和响应原语适应服务器框架:
import { Telemetry } from "telemetry-sh";
const telemetry = new Telemetry(process.env.TELEMETRY_API_KEY);
const allowedMetrics = new Set(["CLS", "INP", "LCP"]);
export async function POST(request: Request) {
if (!isAllowedSameOrigin(request)) {
return new Response("forbidden", { status: 403 });
}
const contentLength = Number(request.headers.get("content-length") || 0);
if (contentLength > 4096 || !(await rateLimit(request))) {
return new Response("rejected", { status: 429 });
}
const input = await request.json();
if (
input.event_name !== "browser_performance_measured" ||
!allowedMetrics.has(input.metric_name) ||
!Number.isFinite(input.metric_value) ||
!isAllowedRouteTemplate(input.route_template)
) {
return new Response("invalid event", { status: 400 });
}
await telemetry.log("browser_performance_measured", {
metric_name: input.metric_name,
metric_value: input.metric_value,
metric_id: boundedString(input.metric_id, 80),
route_template: input.route_template,
release: boundedString(input.release, 80),
navigation_type: boundedCategory(input.navigation_type),
});
return new Response(null, { status: 202 });
}
在生产中,每个服务器进程初始化一次客户端,使用有限的上游超时,并决定遥测失败是否返回 202、204 或可重试的响应。避免浏览器重试循环。网络和会话边界的小速率限制可以减少滥用,但不要在事件中存储原始 IP 地址。
安全和隐私清单
- 仅将
TELEMETRY_API_KEY保留在服务器运行时中。 - 将端点限制为同源请求以及您期望的方法和内容类型。
- 在解析 JSON 之前应用较小的正文限制。
- 允许列表事件名称、字段、类别、路由模板和数字范围。
- 呼叫上行前限速API。
- 除非跨源收集是有意且经过审查的产品,否则请勿使用宽松的 CORS。
- 在收集之前应用您的同意和选择退出规则。
- 定义任何标识符的保留和删除行为。
- 监控被拒绝和速率限制的请求,而无需复制被拒绝的有效负载。
当端点接受凭据或改变用户链接状态时,CSRF 防御仍然很重要。对于匿名、同源测量端点,源验证、严格内容类型和非用户特定的有效负载可能是适当的边界;使用应用程序的安全模型确认该选择。
验证数据
首先部署到非生产环境。确认:
- 浏览器捆绑包不包含 Telemetry 密钥。
- 未知的事件名称、额外字段、原始 URL 和过大的正文将被拒绝。
- 有效的 CLS、INP 和 LCP 事件以数值形式到达。
- 收集请求被阻止或失败不会影响导航。
- 发布和路由值足够稳定,可以进行分组。
- 稀疏路线不用于嘈杂的告警。
使用 按路线和发布配方划分的核心 Web Vitals 在测量旁边保留样本计数。继续使用 使用 SQL 进行前端可靠性监控、JavaScript SDK 导轨 和 脱敏敏感数据。