New Relic Angular統合ガイド - Angularアプリケーションの企業レベル監視
Angularは、TypeScriptベースの堅牢なアーキテクチャと強力な依存性注入システムにより、大規模なエンタープライズアプリケーション開発に最適なフレームワークです。New Relic Browser AgentとAngularの統合により、Angular固有のアーキテクチャパターンに対応した包括的な監視ソリューションを実現できます。
Angular監視の特徴と要件
Angularアプリケーションの監視では、その企業向けアーキテクチャに特化した監視手法が必要です。
Angularアーキテクチャの監視要素
依存性注入システムの活用により、監視機能をサービスとして実装し、アプリケーション全体で一貫した監視を提供できます。ライフサイクルフックの豊富さを利用して、コンポーネントの詳細な動作を追跡し、パフォーマンスボトルネックを特定できます。
RxJSオブザーバブルとの統合により、非同期処理とデータストリームの監視が可能になり、リアクティブプログラミングパターンの効果を測定できます。
TypeScriptによる型安全な監視
Angularの型安全性を活用して、コンパイル時にバグを防止する監視コードを作成できます。
// 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サービスとして実装することで、依存性注入を通じてアプリケーション全体で利用できます。
基本的な監視サービス
// 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);
}
}
}
高度な監視サービス
// 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通信を自動的に監視できます。
// 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のグローバルエラーハンドラーとの統合により、包括的なエラー監視を実現します。
// 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要素レベルでの監視を実装できます。
// 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オペレーターを作成して、オブザーバブルストリームの監視を実装します。
// 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コンポーネントのライフサイクルフックと監視を統合します。
// 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 の変更検知システムに過度な負荷をかけないよう、監視処理を最適化します。遅延実行では、setTimeout
や requestIdleCallback
を使用して、重要でない監視処理を遅延実行します。
まとめ
New RelicとAngularの統合により、エンタープライズレベルのWebアプリケーションにおいて、型安全で保守性の高い監視システムを構築できます。依存性注入、RxJS、TypeScriptといったAngular の強力な機能を活用することで、包括的かつ効率的な監視が実現できます。
次のステップでは、Core Web Vitalsの監視設定について詳しく解説します。ユーザーエクスペリエンスの質を定量化し、SEOとパフォーマンスの両面から最適化を進める方法を学んでいきましょう。
関連記事: Core Web Vitals監視設定関連記事: Vue.js統合ガイド