You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular日志服务中装饰器的正确使用与服务注入方案

实现方案

完全可以在不侵入auth.service原有业务代码的前提下,通过装饰器完成日志埋点,核心是通过Angular根注入器动态获取Analytics服务实例,避免在Auth服务中显式依赖Analytics服务。

核心原理

TypeScript方法装饰器在类编译加载阶段就会执行,此时Angular依赖注入容器还未完成初始化,且装饰器本身不在Angular的DI上下文内,无法直接通过构造器注入服务。因此需要提前缓存Angular根注入器实例,在被装饰的方法实际调用时,再从注入器中获取Analytics服务,既不破坏原有依赖关系,也能正常调用服务方法。

具体实现步骤

1. 改造Log装饰器

修改装饰器文件libs/state/analytics/src/lib/decorators/analytics.decorator.ts,代码如下:

import { Injector } from '@angular/core';
import { AnalyticsService } from 'libs/authentication/auth/src/lib/services/analytics.service';

// 缓存根注入器实例
let rootInjector: Injector;
// 暴露注入器初始化方法
export const initLogDecoratorInjector = (injector: Injector) => {
  rootInjector = injector;
};

export const Log = (eventName?: string) => {
  return function errorCatch(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    // 重写原方法
    descriptor.value = async function (...args: any[]) {
      // 方法执行时再获取服务实例,保证DI容器已初始化完成
      const analyticsService = rootInjector?.get(AnalyticsService, null);
      
      try {
        // 执行原方法,await保证能捕获异步错误
        const result = await originalMethod.apply(this, args);
        return result;
      } catch (error) {
        // 存在Analytics服务时上报错误
        if (analyticsService) {
          analyticsService.logEvent({
            name: eventName || `${target.constructor.name}.${propertyKey}_error`,
            errorMessage: (error as Error)?.message,
            errorStack: (error as Error)?.stack,
            triggerParams: args
          });
        }
        // 抛出原始错误,不改变原方法的错误逻辑
        throw error;
      }
    };

    return descriptor;
  };
};

2. 应用启动时初始化注入器

在应用启动入口文件(通常是main.ts)中,启动完成后将根注入器传入装饰器:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { initLogDecoratorInjector } from 'libs/state/analytics/src/lib/decorators/analytics.decorator';

platformBrowserDynamic().bootstrapModule(AppModule)
  .then(moduleRef => {
    // 初始化装饰器依赖的注入器
    initLogDecoratorInjector(moduleRef.injector);
  })
  .catch(err => console.error(err));

3. 原有装饰器的问题修复

之前实现的装饰器存在两个会导致功能失效的问题,已在上述代码中修复:

  • 原代码用method.apply(target, args)执行原方法,target是类的原型而非类实例,会导致原方法内部this指向错误,无法访问Auth服务中注入的afAuth、_toastService等属性,改为originalMethod.apply(this, args)即可,方法执行时的this指向当前Auth服务实例
  • 原代码的同步try/catch无法捕获Promise/async方法的异步错误,给原方法执行加await即可捕获到登录、注册等异步方法抛出的reject错误

使用方式

直接在Auth服务需要埋点的方法上加@Log()装饰器即可,不需要修改Auth服务的构造函数,也不需要改动原有业务逻辑,完全符合非侵入要求:

@Log('Auth_login_failed')
public async loginWithEmailAndPassword(email: string, password: string)
{
  return this.afAuth.signInWithEmailAndPassword(email, password)
            .then(() => {
              this._logger.log(() => `AuthService.signInWithEmailAndPassword: Successfully logged user in with Email and Password.`);
            })
            .catch((error) => {
              this._throwError(error);
            });
}

注意事项

  • 禁止在装饰器工厂执行阶段(即return errorCatch外层逻辑)获取服务实例,此时Angular尚未完成启动,注入器为空,无法获取到服务
  • 调用injector.get时传入第二个参数null作为默认值,避免Analytics服务未加载时直接抛出错误阻断正常业务流程
  • 如果需要上报成功事件,直接在try块内原方法执行完成后调用logEvent即可

内容的提问来源于stack exchange,提问作者Philip Mutua

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 22:57:25