New Relic Angular統合ガイド - Angularアプリケーションの企業レベル監視

Angularは、TypeScriptベースの堅牢なアーキテクチャと強力な依存性注入システムにより、大規模なエンタープライズアプリケーション開発に最適なフレームワークです。New Relic Browser AgentとAngularの統合により、Angular固有のアーキテクチャパターンに対応した包括的な監視ソリューションを実現できます。

Angular監視の特徴と要件

Angularアプリケーションの監視では、その企業向けアーキテクチャに特化した監視手法が必要です。

Angularアーキテクチャの監視要素

依存性注入システムの活用により、監視機能をサービスとして実装し、アプリケーション全体で一貫した監視を提供できます。ライフサイクルフックの豊富さを利用して、コンポーネントの詳細な動作を追跡し、パフォーマンスボトルネックを特定できます。

RxJSオブザーバブルとの統合により、非同期処理とデータストリームの監視が可能になり、リアクティブプログラミングパターンの効果を測定できます。

TypeScriptによる型安全な監視

Angularの型安全性を活用して、コンパイル時にバグを防止する監視コードを作成できます。

typescript
// New Relic監視のための型定義
interface NewRelicEvent {
  eventName: string;
  attributes: Record<string, any>;
  timestamp?: number;
}

interface ComponentPerformanceMetrics {
  componentName: string;
  initTime: number;
  renderTime: number;
  lifecycleStage: 'OnInit' | 'AfterViewInit' | 'OnDestroy';
}

interface ApiCallMetrics {
  endpoint: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  duration: number;
  statusCode: number;
  success: boolean;
}

Angular サービスとしての実装

New Relic監視機能をAngularサービスとして実装することで、依存性注入を通じてアプリケーション全体で利用できます。

基本的な監視サービス

typescript
// services/new-relic.service.ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class NewRelicService {
  private isNewRelicLoaded(): boolean {
    return typeof (window as any).newrelic !== 'undefined';
  }
  
  trackEvent(event: NewRelicEvent): void {
    if (this.isNewRelicLoaded()) {
      (window as any).newrelic.addPageAction(event.eventName, {
        ...event.attributes,
        timestamp: event.timestamp || Date.now(),
        framework: 'Angular',
        userAgent: navigator.userAgent
      });
    }
  }
  
  trackError(error: Error, context?: Record<string, any>): void {
    if (this.isNewRelicLoaded()) {
      (window as any).newrelic.noticeError(error, {
        ...context,
        angularError: true,
        timestamp: Date.now()
      });
    }
  }
  
  trackComponentPerformance(metrics: ComponentPerformanceMetrics): void {
    this.trackEvent({
      eventName: 'angular_component_performance',
      attributes: metrics
    });
  }
  
  trackApiCall(metrics: ApiCallMetrics): void {
    this.trackEvent({
      eventName: 'angular_api_call',
      attributes: metrics
    });
  }
  
  setCustomAttribute(key: string, value: any): void {
    if (this.isNewRelicLoaded()) {
      (window as any).newrelic.setCustomAttribute(key, value);
    }
  }
}

高度な監視サービス

typescript
// services/advanced-monitoring.service.ts
import { Injectable, OnDestroy } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { NewRelicService } from './new-relic.service';

@Injectable({
  providedIn: 'root'
})
export class AdvancedMonitoringService implements OnDestroy {
  private destroy$ = new Subject<void>();
  private routeStartTime: number = 0;
  
  constructor(
    private router: Router,
    private newRelic: NewRelicService
  ) {
    this.setupRouteTracking();
  }
  
  private setupRouteTracking(): void {
    // ルート変更開始の記録
    this.router.events.pipe(
      filter(event => event instanceof NavigationStart),
      takeUntil(this.destroy$)
    ).subscribe(() => {
      this.routeStartTime = performance.now();
    });
    
    // ルート変更完了の記録
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd),
      takeUntil(this.destroy$)
    ).subscribe((event: NavigationEnd) => {
      const navigationTime = performance.now() - this.routeStartTime;
      
      this.newRelic.trackEvent({
        eventName: 'angular_navigation',
        attributes: {
          url: event.url,
          urlAfterRedirects: event.urlAfterRedirects,
          navigationTime,
          timestamp: Date.now()
        }
      });
    });
  }
  
  trackFormSubmission(formName: string, isValid: boolean, fieldCount: number): void {
    this.newRelic.trackEvent({
      eventName: 'angular_form_submission',
      attributes: {
        formName,
        isValid,
        fieldCount,
        timestamp: Date.now()
      }
    });
  }
  
  trackUserInteraction(interactionType: string, elementInfo: any): void {
    this.newRelic.trackEvent({
      eventName: 'angular_user_interaction',
      attributes: {
        interactionType,
        element: elementInfo,
        timestamp: Date.now()
      }
    });
  }
  
  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

HTTP インターセプターとの統合

AngularのHTTPインターセプターを使用して、すべてのHTTP通信を自動的に監視できます。

typescript
// interceptors/new-relic-http.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { NewRelicService } from '../services/new-relic.service';

@Injectable()
export class NewRelicHttpInterceptor implements HttpInterceptor {
  constructor(private newRelic: NewRelicService) {}
  
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const startTime = performance.now();
    
    return next.handle(req).pipe(
      tap({
        next: (event) => {
          if (event instanceof HttpResponse) {
            const duration = performance.now() - startTime;
            
            this.newRelic.trackApiCall({
              endpoint: req.url,
              method: req.method as any,
              duration,
              statusCode: event.status,
              success: true
            });
          }
        },
        error: (error: HttpErrorResponse) => {
          const duration = performance.now() - startTime;
          
          this.newRelic.trackApiCall({
            endpoint: req.url,
            method: req.method as any,
            duration,
            statusCode: error.status,
            success: false
          });
          
          this.newRelic.trackError(error, {
            httpError: true,
            url: req.url,
            method: req.method,
            status: error.status
          });
        }
      })
    );
  }
}

// app.module.ts での登録
import { HTTP_INTERCEPTORS } from '@angular/common/http';

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: NewRelicHttpInterceptor,
      multi: true
    }
  ]
})
export class AppModule {}

エラーハンドリングの実装

Angularのグローバルエラーハンドラーとの統合により、包括的なエラー監視を実現します。

typescript
// error-handler/new-relic-error-handler.ts
import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { NewRelicService } from '../services/new-relic.service';

@Injectable()
export class NewRelicErrorHandler implements ErrorHandler {
  constructor(private injector: Injector) {}
  
  handleError(error: any): void {
    const newRelic = this.injector.get(NewRelicService);
    
    console.error('Global error handler:', error);
    
    // エラーの詳細分析
    const errorInfo = this.analyzeError(error);
    
    newRelic.trackError(error, {
      globalErrorHandler: true,
      errorType: errorInfo.type,
      stackTrace: error.stack,
      message: error.message,
      timestamp: Date.now()
    });
  }
  
  private analyzeError(error: any): { type: string; severity: string } {
    if (error.name === 'ChunkLoadError') {
      return { type: 'chunk_load_error', severity: 'high' };
    }
    
    if (error instanceof TypeError) {
      return { type: 'type_error', severity: 'medium' };
    }
    
    if (error instanceof ReferenceError) {
      return { type: 'reference_error', severity: 'high' };
    }
    
    return { type: 'unknown_error', severity: 'medium' };
  }
}

// app.module.ts での登録
@NgModule({
  providers: [
    {
      provide: ErrorHandler,
      useClass: NewRelicErrorHandler
    }
  ]
})
export class AppModule {}

ディレクティブとカスタム監視

Angularディレクティブを使用して、DOM要素レベルでの監視を実装できます。

typescript
// directives/track-interaction.directive.ts
import { Directive, Input, HostListener, ElementRef } from '@angular/core';
import { NewRelicService } from '../services/new-relic.service';

@Directive({
  selector: '[trackInteraction]'
})
export class TrackInteractionDirective {
  @Input() trackInteraction: string = '';
  @Input() trackContext: any = {};
  
  constructor(
    private el: ElementRef,
    private newRelic: NewRelicService
  ) {}
  
  @HostListener('click', ['$event'])
  onClick(event: MouseEvent): void {
    this.trackEvent('click', event);
  }
  
  @HostListener('input', ['$event'])
  onInput(event: Event): void {
    this.trackEvent('input', event);
  }
  
  @HostListener('focus', ['$event'])
  onFocus(event: FocusEvent): void {
    this.trackEvent('focus', event);
  }
  
  private trackEvent(eventType: string, event: Event): void {
    const element = this.el.nativeElement;
    
    this.newRelic.trackEvent({
      eventName: 'angular_directive_interaction',
      attributes: {
        interactionName: this.trackInteraction,
        eventType,
        elementTag: element.tagName.toLowerCase(),
        elementId: element.id,
        elementClass: element.className,
        context: this.trackContext,
        timestamp: Date.now()
      }
    });
  }
}

// 使用例
// <button trackInteraction="purchase-button" [trackContext]="{ productId: product.id }">
//   購入する
// </button>

RxJSオペレーターとの統合

RxJSオペレーターを作成して、オブザーバブルストリームの監視を実装します。

typescript
// operators/track-observable.operator.ts
import { Observable } from 'rxjs';
import { tap, catchError, finalize } from 'rxjs/operators';
import { throwError } from 'rxjs';

export interface ObservableTrackingConfig {
  operationName: string;
  context?: any;
  trackStart?: boolean;
  trackComplete?: boolean;
  trackError?: boolean;
}

export function trackObservable<T>(
  config: ObservableTrackingConfig,
  newRelicService: any
) {
  return (source: Observable<T>): Observable<T> => {
    const startTime = performance.now();
    
    if (config.trackStart) {
      newRelicService.trackEvent({
        eventName: 'rxjs_observable_start',
        attributes: {
          operation: config.operationName,
          context: config.context,
          timestamp: Date.now()
        }
      });
    }
    
    return source.pipe(
      tap(() => {
        if (config.trackComplete) {
          const duration = performance.now() - startTime;
          
          newRelicService.trackEvent({
            eventName: 'rxjs_observable_complete',
            attributes: {
              operation: config.operationName,
              duration,
              context: config.context,
              timestamp: Date.now()
            }
          });
        }
      }),
      
      catchError((error) => {
        if (config.trackError) {
          const duration = performance.now() - startTime;
          
          newRelicService.trackError(error, {
            rxjsError: true,
            operation: config.operationName,
            duration,
            context: config.context
          });
        }
        
        return throwError(error);
      }),
      
      finalize(() => {
        const duration = performance.now() - startTime;
        
        newRelicService.trackEvent({
          eventName: 'rxjs_observable_finalized',
          attributes: {
            operation: config.operationName,
            duration,
            context: config.context,
            timestamp: Date.now()
          }
        });
      })
    );
  };
}

// 使用例
this.http.get('/api/products')
  .pipe(
    trackObservable({
      operationName: 'fetchProducts',
      context: { category: 'electronics' },
      trackStart: true,
      trackComplete: true,
      trackError: true
    }, this.newRelic)
  )
  .subscribe(products => {
    this.products = products;
  });

ライフサイクルフック統合

Angularコンポーネントのライフサイクルフックと監視を統合します。

typescript
// mixins/performance-tracking.mixin.ts
import { OnInit, OnDestroy, AfterViewInit } from '@angular/core';
import { NewRelicService } from '../services/new-relic.service';

export abstract class PerformanceTrackingMixin implements OnInit, AfterViewInit, OnDestroy {
  private componentStartTime: number = 0;
  private viewInitTime: number = 0;
  
  constructor(
    protected newRelic: NewRelicService,
    protected componentName: string
  ) {}
  
  ngOnInit(): void {
    this.componentStartTime = performance.now();
    
    this.newRelic.trackEvent({
      eventName: 'angular_component_init',
      attributes: {
        component: this.componentName,
        timestamp: Date.now()
      }
    });
  }
  
  ngAfterViewInit(): void {
    this.viewInitTime = performance.now();
    const initDuration = this.viewInitTime - this.componentStartTime;
    
    this.newRelic.trackComponentPerformance({
      componentName: this.componentName,
      initTime: initDuration,
      renderTime: initDuration,
      lifecycleStage: 'AfterViewInit'
    });
  }
  
  ngOnDestroy(): void {
    const totalLifetime = performance.now() - this.componentStartTime;
    
    this.newRelic.trackEvent({
      eventName: 'angular_component_destroy',
      attributes: {
        component: this.componentName,
        lifetime: totalLifetime,
        timestamp: Date.now()
      }
    });
  }
}

// 使用例
export class ProductListComponent extends PerformanceTrackingMixin {
  constructor(private newRelicService: NewRelicService) {
    super(newRelicService, 'ProductListComponent');
  }
  
  // コンポーネント固有の実装
}

実装のベストプラクティス

Angular環境でのNew Relic監視を最適化するための推奨事項をまとめます。

アーキテクチャ最適化

依存性注入の活用により、監視機能をシングルトンサービスとして提供し、メモリ効率を最適化します。モジュール分割では、監視機能を専用のモジュールに分離し、必要に応じて遅延読み込みを実装します。

TypeScript活用

型安全性の確保により、コンパイル時にバグを防止し、監視データの一貫性を保証します。インターフェース定義では、明確なデータ構造を定義して、チーム間での理解を促進します。

パフォーマンス考慮

Zone.js との協調により、Angular の変更検知システムに過度な負荷をかけないよう、監視処理を最適化します。遅延実行では、setTimeoutrequestIdleCallback を使用して、重要でない監視処理を遅延実行します。

まとめ

New RelicとAngularの統合により、エンタープライズレベルのWebアプリケーションにおいて、型安全で保守性の高い監視システムを構築できます。依存性注入、RxJS、TypeScriptといったAngular の強力な機能を活用することで、包括的かつ効率的な監視が実現できます。

次のステップでは、Core Web Vitalsの監視設定について詳しく解説します。ユーザーエクスペリエンスの質を定量化し、SEOとパフォーマンスの両面から最適化を進める方法を学んでいきましょう。


関連記事: Core Web Vitals監視設定関連記事: Vue.js統合ガイド