性能排障的证据留存
性能排障的证据留存1. 线上排障用户反馈卡顿但日志不足线上排障中常见一种情况用户在复杂表格页连续点击导出和筛选时感到页面无响应但 Sentry 和后端日志没有对应的 Error 堆栈。本地设备和网络条件与用户现场不同复现可能失败。没有现场性能数据时不应直接把问题归因为用户设备性能。这就是典型的排障证据链缺失。前端性能诊断既可以在 CI/CD 阶段检查预算也可以在生产环境记录长任务、内存指标、User Timing 和 Trace ID。采集范围应兼顾可用性、隐私和上报成本这些数据有助于复现但通常不能单独构成完整证据。2. 什么是“有效的性能排障证据”性能诊断通常可保留以下信息精确的时间线与阻塞耗时Duration Start Time通过PerformanceLongTaskTiming捕获单次超过 50ms 的长任务。触发上下文Attribution / Target Component记录用户操作、路由和可获取的组件标识。Long Task 的浏览器归因能力有限不能保证定位到具体组件。内存指标Heap Metric在支持的浏览器中记录jsHeapSizeLimit与usedJSHeapSize。这些字段不是标准跨浏览器能力应视为可选信息。统一的 Trace ID 关联将前端的组件渲染 Span 与后端的 HTTP API 响应 Trace 全链路串联。下面是不同诊断手段在证据保留维度的对比证据采集维度传统 Console / Sentry确定性性能 Trace 证据链卡顿捕获能力侧重错误事件配置不同能力不同可记录支持浏览器中的 Long Task但不能覆盖所有卡顿现场快照上下文取决于 SDK 配置可附加内存、DOM 计数与 User Timing字段受浏览器限制性能预算比对需额外配置可与 CI 预算或历史基线比较3. 生产级性能证据链采集器实现下面是一个在浏览器端收集 Long Task 的 TypeScript 示例。虽然文件可由 Node.js 工具链构建但其中的window、PerformanceObserver和performance.memory均依赖浏览器环境import { z } from zod; // 1. 结构化性能证据 Schema确保证据链标准统一 export const PerformanceEvidenceSchema z.object({ traceId: z.string().uuid(), taskType: z.enum([long-task, memory-spike, layout-shift]), durationMs: z.number(), startTime: z.number(), url: z.string(), activeComponent: z.string().optional(), memoryUsageMb: z.number().optional(), userTimingSpans: z.array( z.object({ name: z.string(), duration: z.number(), }) ), }); export type PerformanceEvidence z.infertypeof PerformanceEvidenceSchema; // 2. 证据链收集器 export class PerformanceTraceCollector { private traceId: string; private spans: Array{ name: string; duration: number } []; constructor() { this.traceId crypto.randomUUID(); } // 手动标记组件渲染 Span public startSpan(spanName: string): () void { const startMark ${spanName}-start-${Date.now()}; const endMark ${spanName}-end-${Date.now()}; performance.mark(startMark); return () { performance.mark(endMark); try { performance.measure(spanName, startMark, endMark); const entries performance.getEntriesByName(spanName, measure); const lastEntry entries[entries.length - 1]; if (lastEntry) { this.spans.push({ name: spanName, duration: Math.round(lastEntry.duration) }); } } catch (e) { // 清理 mark 标记 } }; } // 监听 Production 模式下的 Long Task 长任务证据 public observeLongTasks(onEvidenceCollected: (evidence: PerformanceEvidence) void) { if (typeof window undefined || !(PerformanceObserver in window)) return; const observer new PerformanceObserver((entryList) { for (const entry of entryList.getEntries()) { // 获取当前内存使用量 const memoryInfo (performance as any).memory; const usedMb memoryInfo ? Math.round(memoryInfo.usedJSHeapSize / 1024 / 1024) : undefined; const rawEvidence { traceId: this.traceId, taskType: long-task as const, durationMs: Math.round(entry.duration), startTime: Math.round(entry.startTime), url: window.location.href, memoryUsageMb: usedMb, userTimingSpans: [...this.spans], }; const validated PerformanceEvidenceSchema.safeParse(rawEvidence); if (validated.success) { onEvidenceCollected(validated.data); } } }); try { observer.observe({ entryTypes: [longtask] }); } catch (err) { console.warn(LongTask 监听不可用:, err); } } }4. 总结用数据证据代替口头扯皮采集机制可以在用户反馈卡顿时提供长任务时长、路由、用户操作和已记录的 span 等线索。要定位到具体按钮或组件还需要业务埋点、采样策略与复现结果相互印证。责任边界最终要体现在接口上协作卡住时先找谁提供数据、谁维护接口、谁决定失败后的业务动作。负责人名单只能解决联络问题真正的边界还要进入请求结构、状态转换、错误码和发布流程。调用方不能依赖文档外的默认行为提供方也不能在没有兼容说明时新增必填字段或改变重试语义。涉及异步处理时还要约定取消是停止等待还是终止工作以及迟到结果由谁接收。契约测试应同时保留旧调用样例和新行为样例检查缺字段、重复请求、乱序、超时与权限不足。配置、网络策略和真实依赖不在普通单元测试范围内目标环境仍需小范围联调。变更单里写明所有者、影响方、兼容窗口、监控信号和回退入口出现问题时团队可以按版本与状态讨论而不是互相猜测。边界清楚的标志不是文档篇幅长而是失败发生后能迅速判断由哪一层处理。