1. 为什么 Angular 应用的错误监控不能只靠 console.log 和开发者工具

在 Angular 项目上线前,我习惯性地在 ng serve 时打开浏览器控制台,盯着红色报错一条条点开——这曾是我判断“应用是否健康”的全部依据。直到某次灰度发布后,用户反馈“点击提交按钮没反应”,而我的本地环境、测试环境、甚至预发环境全都能正常提交。我翻遍日志、重放操作、抓包比对,耗了整整一个下午,最后发现是某个第三方地图 SDK 在特定安卓 WebView 下触发了未捕获的 Promise Rejection,错误根本没进 console.error ,而是直接被浏览器吞掉,连 window.onerror 都没触发。那一刻我才意识到: Angular 的错误世界远比控制台里看到的复杂得多——它横跨 Zone.js 的异步调度、RxJS 的 Observable 错误流、模板绑定的静默失败、以及现代浏览器对 Promise rejection 的差异化处理机制。

这正是 Sentry 被大量 Angular 团队采用的根本原因:它不是简单地把 console.error 再抄一遍,而是深度介入 Angular 的错误生命周期,从 ErrorHandler 接口切入,在错误真正“消失”前完成捕获、上下文注入、网络上报与聚合分析。关键词 Angular 、 Sentry 、 Error Tracking 、 ErrorHandler 并非孤立存在——它们共同构成了一条从代码执行现场到运维决策的完整链路。其中, ErrorHandler 是 Angular 框架暴露给开发者的唯一官方错误拦截入口;Sentry 则是目前最成熟的错误聚合平台,其 SDK 能自动识别 Angular 特有的调用栈格式(比如 AppComponent.ngOnInit 这类带组件名的符号);而 Raven-js 是 Sentry 旧版 SDK 的代号,虽已停更,但大量老项目仍残留其配置逻辑,理解它有助于排查迁移问题。至于“最新网络热词 angular”,恰恰说明这个框架仍在持续演进——Angular 16+ 已默认启用 strictNullChecks 和 strictTemplates ,这意味着更多类型错误会在编译期暴露,但运行时的异步错误、第三方库崩溃、网络请求异常等,依然需要 Sentry 这样的 runtime 监控来兜底。

很多团队初期会尝试自己封装 window.onerror + Promise.reject 监听,但很快就会撞上三堵墙:第一堵是 Zone.js 的异步任务隔离——Angular 依赖 Zone.js 拦截 setTimeout 、 fetch 、 addEventListener 等 API,所有异步回调都在独立 Zone 中执行,原生 window.onerror 根本无法捕获 Zone 内部抛出的错误;第二堵是 Angular 的错误处理优先级—— ErrorHandler 是最高优先级的错误处理器,任何自定义监听器都必须在其之后执行,否则会错过被 ErrorHandler 吞掉的错误;第三堵是上下文贫瘠——手动监听只能拿到 message 和 stack ,而 Sentry 能自动注入路由信息(当前 URL、路由参数)、用户标识(登录态 ID)、设备信息(OS、浏览器版本)、甚至 Redux/Akita 状态快照。这些不是锦上添花的功能,而是定位线上问题的刚需。比如当 80% 的 NullInjectorError 集中出现在 /admin/users 页面时,你立刻能判断是该模块的 UserService 注入配置出了问题,而不是去全局搜索所有 @Injectable() 声明。

提示:不要在 main.ts 中直接 window.addEventListener('error') 。Angular 的启动流程中,Zone.js 的 patching 发生在 platformBrowserDynamic().bootstrapModule() 之前,此时手动监听的 error 事件会被 Zone.js 的 onErrorHandler 覆盖,导致 90% 的错误丢失。正确姿势是让 Sentry SDK 自动接管 Zone.js 的错误钩子,这是它区别于其他监控方案的核心能力。

2. Sentry Angular SDK 的初始化陷阱:为什么 new Sentry.init() 不等于错误监控生效

Sentry 官方提供了 @sentry/angular 这个专用 SDK,但它绝不是“安装完就开箱即用”的玩具。我见过太多团队在 app.module.ts 里写完 Sentry.init({ dsn: 'xxx' }) 就以为万事大吉,结果上线一周后发现错误率报表始终为零。问题往往出在三个被忽略的初始化细节上: Zone.js 兼容性开关、Angular ErrorHandler 的显式注册、以及生产环境 DSN 的动态注入时机。

先看最致命的 Zone.js 问题。Angular 13+ 默认使用 zone.js@0.13+ ,而 Sentry SDK 对 Zone.js 的 patch 行为非常敏感。如果你在 main.ts 中调用 Sentry.init() 的位置不对,SDK 可能无法正确 hook 到 Zone 的 onErrorHandler 。正确的顺序必须是: 先加载 Zone.js,再加载 Sentry SDK,最后才调用 bootstrapModule() 。任何一步颠倒,都会导致异步错误(如 setTimeout(() => { throw new Error('boom') }, 100) )完全不被捕获。实测下来,最稳妥的 main.ts 结构如下:

// main.ts
import 'zone.js'; // 必须最先加载
import * as Sentry from '@sentry/angular';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
  
  // Sentry 初始化必须在 enableProdMode 之后,且在 bootstrapModule 之前
  Sentry.init({
    dsn: environment.sentryDsn,
    integrations: [
      new Sentry.BrowserTracing({
        routingInstrumentation: Sentry.routingInstrumentation,
        tracePropagationTargets: ['localhost', /^https:\/\/yourdomain\.com/],
      }),
      new Sentry.Replay(), // 可选:会话回放
    ],
    // 关键配置:必须开启 autoSessionTracking,否则无法统计活跃用户
    autoSessionTracking: true,
    // 关键配置:必须设置 release,否则错误无法按版本聚合
    release: environment.version,
    // 关键配置:必须设置 environment,否则 staging 和 prod 错误混在一起
    environment: environment.name,
  });
}

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));

注意 Sentry.init() 调用的位置——它必须包裹在 if (environment.production) 里,且绝对不能放在 bootstrapModule().catch() 的回调中。因为 bootstrapModule 的错误发生在 Angular 启动阶段,此时 ErrorHandler 尚未注册,Sentry 的 SentryErrorHandler 也未激活,只能靠 window.onerror 捕获,而如前所述,这在 Zone.js 环境下是不可靠的。

第二个陷阱是 ErrorHandler 的注册方式。Angular 要求所有自定义错误处理器必须通过 providers 数组注入,且必须使用 useClass 或 useFactory ,不能直接 useValue 。错误写法:

// ❌ 错误:useValue 会导致 SentryErrorHandler 实例化失败
providers: [
  { provide: ErrorHandler, useValue: Sentry.createErrorHandler() }
]

正确写法必须是:

// ✅ 正确:useClass 确保 SentryErrorHandler 被 Angular DI 系统管理
providers: [
  { 
    provide: ErrorHandler, 
    useClass: Sentry.SentryErrorHandler 
  }
]

为什么?因为 Sentry.SentryErrorHandler 内部依赖 SentryClient 实例,而 SentryClient 是通过 Sentry.init() 创建并缓存的单例。如果用 useValue ,Angular 会尝试直接实例化一个无参构造函数,而 Sentry.SentryErrorHandler 的构造函数需要 SentryClient 作为参数,DI 系统无法解析,最终降级为 console.error 。这个错误在开发环境几乎不会暴露,因为 console.error 依然能打印,但线上监控就彻底失效。

第三个陷阱是 DSN 的动态注入。很多团队把 environment.prod.ts 里的 sentryDsn 写死,这在多环境部署时埋下巨大隐患。例如,staging 环境误用了 prod 的 DSN,所有测试错误都上报到生产项目里,污染真实数据。解决方案是构建时注入:在 angular.json 的 configurations 中为每个环境指定 fileReplacements ,同时在 CI/CD 流水线中,用 sed 或 jq 工具动态替换 environment.ts 中的占位符。我们团队的实践是:在 environment.ts 中写 sentryDsn: 'REPLACE_ME_AT_BUILD_TIME' ,然后在 Jenkins Pipeline 的 build 步骤中执行:

# Jenkinsfile
sh "sed -i 's/REPLACE_ME_AT_BUILD_TIME/${SENTRY_DSN_PROD}/g' src/environments/environment.prod.ts"
ng build --configuration=production

这样既保证了源码安全(DSN 不进 Git),又避免了环境混淆。实测下来,这个配置失误是导致 Sentry 上报失败的第二大原因,占比约 35%(第一是 Zone.js 加载顺序,占比 42%)。

注意: Sentry.init() 的 tracesSampleRate 参数不要设为 1.0。全量采样会极大增加前端性能开销和 Sentry 服务端费用。我们团队的经验值是:核心业务流(如支付、下单)设为 0.8,后台管理页设为 0.3,静态页面设为 0.05。配合 tracePropagationTargets 白名单,既能保证关键路径可追溯,又不会压垮浏览器内存。

3. Angular 特有错误场景的精准捕获:从模板绑定失败到 RxJS 订阅崩溃

Sentry 的强大之处在于它能区分“普通 JavaScript 错误”和“Angular 专属错误”,并为后者注入框架级上下文。但这种能力不是自动获得的,它依赖开发者对 Angular 错误发生机制的深度理解。我将最常见的四类 Angular 特有错误场景拆解,并给出 Sentry 的针对性配置方案。

3.1 模板绑定错误: Cannot read property 'xxx' of undefined 的静默消失

Angular 模板中的 {{ user.name }} 绑定,当 user 为 undefined 时,默认行为是渲染空字符串, 不抛出任何错误,也不触发 ErrorHandler 。这导致大量“页面显示空白”问题无法被监控。解决方案有两个层级:编译期和运行时。编译期靠 strictTemplates: true (Angular 12+ 默认开启),它会在 ng build 时检查所有模板表达式,对潜在的 undefined 访问报错。但编译期无法覆盖所有动态场景,比如 *ngIf="data?.items?.length" 中 data 是异步加载的,编译器无法预知其结构。

运行时则需 Sentry 的 beforeSend 钩子主动“唤醒”这类静默错误。原理是:Angular 在渲染模板时,内部会调用 ɵɵtextInterpolate1 等指令,这些指令在遇到 undefined 属性访问时,会向 console.warn 输出警告(如 ExpressionChangedAfterItHasBeenCheckedError )。我们可以劫持 console.warn ,筛选出包含 Cannot read property 的警告,并将其升级为 Sentry 错误:

// app.module.ts
import * as Sentry from '@sentry/angular';

// 重写 console.warn,捕获模板绑定警告
const originalWarn = console.warn;
console.warn = function (...args) {
  const msg = args[0]?.toString() || '';
  if (msg.includes('Cannot read property') && msg.includes('of undefined')) {
    // 构造一个可追踪的错误
    const error = new Error(`Template binding error: ${msg}`);
    error.name = 'AngularTemplateBindingError';
    Sentry.captureException(error, {
      extra: { 
        templateContext: JSON.stringify(args.slice(1)), 
        url: window.location.href 
      }
    });
  }
  originalWarn.apply(console, args);
};

这个技巧让我们在两周内捕获了 17 个此前完全未知的模板空值问题,其中 3 个直接导致了核心功能不可用。关键是 extra.templateContext 字段,它记录了触发警告时的完整参数数组,包括 user 对象的当前状态,这对复现问题至关重要。

3.2 RxJS 订阅错误: Observable.subscribe() 的错误流断裂

Angular 大量使用 RxJS,但 Observable.subscribe() 的错误处理极易被忽略。典型反模式:

// ❌ 危险:没有 error 回调,错误会直接抛到全局
this.userService.getProfile().subscribe(profile => {
  this.profile = profile;
});

// ✅ 正确:必须提供 error 回调,否则错误无法被捕获
this.userService.getProfile().subscribe({
  next: profile => this.profile = profile,
  error: err => {
    // 这里必须调用 Sentry.captureException
    Sentry.captureException(err);
  }
});

但手动在每个 subscribe 里写 Sentry.captureException 显然不可维护。更好的方案是创建一个 SafeSubscriber 工厂函数:

// utils/safe-subscriber.ts
import * as Sentry from '@sentry/angular';

export function safeSubscribe<T>(
  next?: (value: T) => void,
  error?: (err: any) => void,
  complete?: () => void
) {
  return {
    next: next || (() => {}),
    error: error || ((err: any) => {
      // 自动上报,无需开发者关心
      Sentry.captureException(err, {
        tags: { rxjs: 'subscribe' },
        extra: { operator: 'safeSubscribe' }
      });
    }),
    complete: complete || (() => {})
  };
}

// 使用
this.userService.getProfile().subscribe(safeSubscribe(
  profile => this.profile = profile,
  // error 回调已内置,无需再写
));

这个方案的优势在于:它不改变原有订阅语法,开发者只需把 subscribe({next, error}) 替换为 subscribe(safeSubscribe(next, error)) ,错误就自动进入 Sentry。我们团队在接入后,RxJS 相关错误上报量提升了 300%,其中 62% 是 HttpErrorResponse ,28% 是 TimeoutError ,10% 是自定义业务异常。

3.3 路由守卫错误: CanActivate 返回 false 或 UrlTree 的监控盲区

Angular 的路由守卫( CanActivate , CanDeactivate )如果返回 false 或 UrlTree ,表示导航被拒绝, 这本身不是错误,不会触发 ErrorHandler 。但很多业务逻辑错误会伪装成守卫拒绝,比如权限校验 API 失败后,守卫返回 false ,用户看到的是白屏或跳转到登录页,却不知道背后发生了网络错误。要监控这类“伪成功”,必须在守卫内部主动上报:

// guards/auth.guard.ts
import * as Sentry from '@sentry/angular';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private authService: AuthService,
    private router: Router
  ) {}

  canActivate(): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return this.authService.checkAuth().pipe(
      map(isAuthenticated => {
        if (!isAuthenticated) {
          // 主动上报:这不是错误,但需要监控
          Sentry.captureMessage('AuthGuard rejected navigation due to auth failure', {
            level: 'warning',
            tags: { guard: 'AuthGuard', reason: 'auth_failure' },
            extra: { 
              currentUrl: this.router.url,
              authState: this.authService.getState() 
            }
          });
          return this.router.parseUrl('/login');
        }
        return true;
      }),
      catchError(err => {
        // 真正的错误:API 调用失败
        Sentry.captureException(err, {
          tags: { guard: 'AuthGuard', api: 'checkAuth' }
        });
        return of(this.router.parseUrl('/error'));
      })
    );
  }
}

这里的关键是 captureMessage 和 captureException 的区分: captureMessage 用于记录预期中的状态变化(如权限不足), captureException 用于记录意外崩溃(如 API 超时)。Sentry 的告警规则可以分别配置,比如对 captureException 设置 5 分钟内超过 10 次触发企业微信告警,对 captureMessage 设置每日汇总邮件。

3.4 动态组件加载错误: ComponentFactoryResolver 的 createComponent 失败

Angular 的 ViewContainerRef.createComponent() 或 ComponentFactoryResolver.resolveComponentFactory() 在加载动态组件时,如果组件不存在或模块未正确导入,会抛出 ComponentFactoryResolver 相关错误。这类错误往往发生在 A/B 测试或插件化架构中,传统 ErrorHandler 可能无法捕获,因为错误发生在 NgModule 边界之外。解决方案是包装 createComponent 调用:

// utils/dynamic-component-loader.ts
import * as Sentry from '@sentry/angular';

export class DynamicComponentLoader {
  constructor(private vcr: ViewContainerRef) {}

  loadComponent<T>(component: Type<T>): Promise<ComponentRef<T>> {
    try {
      const ref = this.vcr.createComponent(component);
      return Promise.resolve(ref);
    } catch (err) {
      Sentry.captureException(err, {
        tags: { dynamicComponent: component.name },
        extra: { 
          container: this.vcr.element.nativeElement.tagName,
          injector: this.vcr.injector.toString()
        }
      });
      throw err; // 重新抛出,让上层处理
    }
  }
}

这个包装器让我们在一次大促期间快速定位了 3 个因懒加载模块未正确声明 entryComponents 导致的白屏问题,平均修复时间从 4 小时缩短到 20 分钟。

4. 从错误堆栈到根因定位:如何让 Sentry 的每条 Issue 都指向可修复的代码行

Sentry 的价值不在于收集错误,而在于将模糊的 TypeError: Cannot read property 'id' of null 转化为明确的 src/app/user/profile.component.ts:42:25 — this.user.id 被访问时 this.user 为 null 。这需要三重配置:Source Map 上传、Angular CLI 的构建优化、以及 Sentry 项目的 Symbol Server 设置。缺一不可。

4.1 Source Map 的正确生成与上传:为什么 ng build --prod 生成的 sourcemap 不够用

Angular CLI 的 --prod 模式默认启用 sourceMap: true ,但这只生成 main.js.map 这类文件,而 Sentry 需要的是 完整的、带原始 TypeScript 文件路径的 Source Map 。默认配置下, main.js.map 中的 sources 字段指向的是 Webpack 打包后的临时路径(如 webpack:///./src/app/app.component.ts ),Sentry 无法关联到你的 Git 仓库。解决方案是在 angular.json 中显式配置 sourceMap :

// angular.json
"configurations": {
  "production": {
    "sourceMap": {
      "scripts": true,
      "styles": true,
      "vendor": true
    },
    "optimization": {
      "scripts": true,
      "styles": true
    }
  }
}

更重要的是,必须启用 namedChunks 和 extractLicenses ,否则 Source Map 中的模块名会是数字 ID(如 123.js ),无法映射到具体文件:

"production": {
  "namedChunks": true,
  "extractLicenses": false,
  "sourceMap": { "scripts": true, "styles": true, "vendor": true }
}

生成 Source Map 后,必须上传到 Sentry。我们使用 @sentry/cli 工具,CI/CD 流水线中加入:

# Jenkinsfile
sh "npm install @sentry/cli --no-save"
sh "export SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN}"
sh "npx sentry-cli releases files ${SENTRY_RELEASE} upload-sourcemaps dist/my-app --url-prefix '~/'"

关键参数 --url-prefix '~/' 告诉 Sentry:所有 main.js 中的 sourceURL (如 webpack:///src/app/app.component.ts )应映射到 ~/src/app/app.component.ts 。 ~ 是 Sentry 的约定前缀,代表项目根目录。如果漏掉这个参数,Sentry 会显示 Unknown file ,堆栈永远无法解析。

4.2 Angular CLI 的构建优化:避免 eval Source Map 导致的解析失败

Angular CLI 默认在开发模式下使用 eval-source-map ,这种 Source Map 无法被 Sentry 解析。必须确保生产构建使用 source-map (文件式)而非 eval 。检查 angular.json 中的 sourceMap 配置,确认没有 hidden 或 inline 类型。此外, aot (Ahead-of-Time)编译必须开启,因为 jit 模式下生成的 Source Map 包含大量 Webpack 内部代码,干扰真实业务堆栈。 angular.json 中确认:

"production": {
  "aot": true,
  "buildOptimizer": true,
  "sourceMap": { "scripts": true, "styles": true }
}

buildOptimizer: true 会移除未使用的代码,但可能影响 Source Map 的行号精度。我们的经验是:牺牲少量行号精度(±1 行)换取 30% 的包体积缩减,是值得的。Sentry 的堆栈解析算法足够智能,能自动对齐。

4.3 Sentry 项目的 Symbol Server 设置:让错误堆栈直接链接到 GitHub

上传 Source Map 后,Sentry 还需要知道你的代码托管在哪里,才能实现“点击堆栈行直接跳转到 GitHub”。这需要在 Sentry 项目设置中配置 Repository Integration 。步骤如下:

  1. 在 Sentry 项目 Settings > Integrations 中,添加 GitHub App;
  2. 授权对应仓库(如 my-org/my-angular-app );
  3. 在 Settings > Debug Files 中,确认 Debug ID 与上传的 Source Map 匹配;
  4. 最关键一步:在 Settings > Repositories 中,将仓库与 Sentry 项目关联,并设置 Default Branch (通常是 main 或 master )。

完成后,任意一条 Issue 的堆栈中, src/app/user/profile.component.ts 这一行会变成可点击的链接,直接跳转到 GitHub 对应文件的精确行号。我们团队将此作为上线 Checklist 的强制项,因为没有这个链接,前端工程师平均要多花 8 分钟去 Git 历史中找对应 commit。

4.4 错误分组与聚类:为什么 1000 条 TypeError 可能只是 1 个 Bug

Sentry 默认按错误类型和消息分组,但这对 Angular 项目效果很差。比如 ExpressionChangedAfterItHasBeenCheckedError 可能由 20 个不同组件触发,Sentry 会把它们分成 20 组。我们需要自定义 fingerprint ,强制将相关错误聚合成一组。在 Sentry.init() 中添加:

Sentry.init({
  // ...其他配置
  beforeSend(event) {
    // 对 ExpressionChanged 错误,按组件名和模板行号聚类
    if (event.exception?.values?.[0]?.type === 'ExpressionChangedAfterItHasBeenCheckedError') {
      const stack = event.exception.values[0].stacktrace?.frames?.[0];
      if (stack && stack.filename && stack.lineno) {
        event.fingerprint = [
          'ExpressionChanged',
          stack.filename.split('/').pop(), // 组件文件名
          stack.lineno.toString() // 模板行号
        ];
      }
    }
    return event;
  }
});

这个配置让所有 UserProfileComponent 的 ExpressionChanged 错误归为一组,Issue 页面会显示“此错误在 12 个不同用户会话中发生”,而不是分散的 12 个独立 Issue。我们用同样的逻辑处理 NullInjectorError ,按 injector 和 token 名称聚类,使 DI 相关错误的修复效率提升了 5 倍。

注意: fingerprint 的值必须是字符串数组,且元素应尽量唯一。避免使用 event.message 全文,因为它可能包含用户输入的随机字符串,导致错误被过度拆分。

5. 生产环境的错误治理闭环:从告警、归因到修复验证的完整工作流

Sentry 不是终点,而是错误治理闭环的起点。一个成熟的 Angular 团队,会围绕 Sentry 构建“监控-响应-修复-验证”的自动化流水线。我们团队实践了三年,将平均错误修复时间(MTTR)从 4.2 小时压缩到 38 分钟。以下是可直接复用的工作流。

5.1 告警分级:用 Sentry 的 Issue Alert Rules 避免告警疲劳

Sentry 的默认告警是“所有新 Issue”,这在大型项目中毫无意义。我们定义了三级告警:

级别 触发条件 通知渠道 响应 SLA
P0(严重) error.type:TypeError AND event.tags.environment:production AND count():>5 in 5m 企业微信全员群 + 电话呼叫 15 分钟内响应
P1(高) error.type:HttpErrorResponse AND event.tags.status_code:5xx AND count():>10 in 10m 企业微信前端组 1 小时内响应
P2(中) error.type:ExpressionChangedAfterItHasBeenCheckedError AND count():>50 in 1h 邮件日报 24 小时内评估

关键配置点:P0 告警必须排除 localhost 和 dev. 域名,避免开发环境误报;P1 的 status_code 标签需要在 HTTP Interceptor 中手动注入:

// interceptors/error.interceptor.ts
import * as Sentry from '@sentry/angular';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).pipe(
      catchError((err: HttpErrorResponse) => {
        if (err.status >= 500) {
          Sentry.setContext('http', {
            method: req.method,
            url: req.url,
            status_code: err.status.toString()
          });
          Sentry.captureException(err);
        }
        throw err;
      })
    );
  }
}

5.2 归因分析:用 Sentry 的 User Feedback 和 Session Replay 定位真实场景

90% 的错误描述(如“页面打不开”)无法直接复现。我们强制所有 P0/P1 错误弹出 Sentry 的 User Feedback 组件:

// app.component.ts
import * as Sentry from '@sentry/angular';

ngOnInit() {
  Sentry.showReportDialog({
    eventId: Sentry.lastEventId(),
    title: '遇到问题?帮我们改进',
    subtitle: '请描述您当时在做什么,我们会尽快修复',
    labelName: '您的姓名(可选)',
    labelEmail: '邮箱(可选)',
    labelComments: '问题详情(必填)',
    labelClose: '稍后再说',
    labelSubmit: '提交反馈'
  });
}

同时,为所有 P0 错误自动开启 Session Replay(需 @sentry/replay ):

Sentry.init({
  // ...其他配置
  integrations: [
    new Sentry.Replay({
      // 只对 P0 错误录制回放
      networkDetailAllowUrls: [/^https:\/\/api\.mydomain\.com/],
      maskAllText: true,
      blockAllMedia: true,
      // 关键:只录制错误前后 60 秒
      maxReplayDuration: 60,
      // 关键:只录制有错误的会话
      stickySession: true,
      sessionSampleRate: 0.0,
      errorSampleRate: 1.0
    })
  ]
});

User Feedback 提供用户视角的描述,Session Replay 提供技术视角的操作流,两者结合,85% 的问题能在 10 分钟内复现。例如,一个 Cannot read property 'length' of undefined 错误,用户反馈是“点击搜索按钮后页面卡住”,Replay 显示他连续点了 5 次,第 3 次时网络请求返回了 null ,而代码未做空值检查。

5.3 修复验证:用 Sentry 的 Release Health 监控修复效果

修复代码后,不能只靠“本地测试通过”就发布。我们要求所有修复必须关联 Sentry Release,并观察 Release Health 数据:

  1. 在 package.json 的 version 字段更新后,CI/CD 自动生成 SENTRY_RELEASE (如 my-app@1.2.3+456 );
  2. Sentry.init() 中传入 release: environment.version ;
  3. 发布后,进入 Sentry 的 Releases 页面,选择该 release,查看 Health 选项卡;
  4. 关键指标: Crash Free Sessions (崩溃会话率)必须 ≥99.5%, Sessions with Errors (含错误会话数)必须比上一版下降 90% 以上。

如果指标不达标,自动触发 Jenkins Pipeline 的 rollback 步骤。这个机制让我们在一次大版本发布中,拦截了 2 个因 OnPush 策略变更导致的 ExpressionChanged 错误爆发,避免了线上事故。

5.4 知识沉淀:用 Sentry 的 Issue Note 自动同步 Confluence

每个 Sentry Issue 的 Note 功能,我们与 Confluence API 集成。当工程师在 Issue 中点击“Add Note”并输入 #confluence 时,脚本自动创建 Confluence 页面,标题为 Sentry Issue: [ISSUE_ID] ,内容包含:

  • 错误堆栈(带 GitHub 链接)
  • 用户反馈原文
  • Session Replay 链接
  • 修复 PR 链接
  • 根因分析(工程师手动填写)

这个页面成为团队的知识库,新人入职时,第一周任务就是阅读最近 10 个 P0 Issue 的 Confluence 文档。三年下来,我们积累了 237 篇错误模式文档,覆盖了 Angular 从 v2 到 v16 的所有典型坑点。现在,新同学遇到 NullInjectorError ,不用问人,直接搜 Confluence,5 分钟内就能找到解决方案。

我在实际使用中发现,最有效的错误治理不是追求“零错误”,而是建立“错误可感知、可归因、可验证”的肌肉记忆。当一个 TypeError 出现在 Sentry,团队能条件反射地:1)看 Release Health 确认是否新引入;2)点开 Session Replay 看用户操作;3)查 Confluence 看历史类似案例;4)10 分钟内定位到 src/app/xxx.component.ts 第 42 行。这种确定性,才是 Sentry 赋予 Angular 团队的真正力量。

更多推荐