소개: 배포는 끝이 아니라 시작입니다
배포 후 어떤 일이 발생하는지 모르면 완벽한 CI/CD 파이프라인은 쓸모가 없습니다. 실제 사용자는 여러 기기에서 예측할 수 없는 방식으로 애플리케이션과 상호 작용합니다. 다양한 연결로 다릅니다. 그만큼 모니터링 그리고경고 DevOps 주기를 종료합니다. 사용자가 문제를 보고하기 전에 문제를 감지할 수 있습니다. 그리고 뭔가가 작동하지 않을 때 신속하게 대응할 수 있습니다.
이 시리즈의 마지막 기사에서 프런트엔드 개발자를 위한 DevOps, 프런트엔드 모니터링의 세 가지 범주인 오류 추적, 성능 모니터링을 살펴보겠습니다. 및 사용자 행동 분석. Angular에서 오류 추적을 위해 Sentry를 구성하고 구현하겠습니다. 웹 바이탈을 통한 실제 사용자 모니터링 및 효과적인 경고 전략을 정의합니다.
이 기사에서 배울 내용
- 프런트엔드 모니터링의 세 가지 범주(오류, 성능, 사용자 행동)
- Sentry를 Angular 애플리케이션에 통합하는 방법
- 실제 사용자 모니터링(RUM) 구현 방법
- 종합 모니터링(가동시간 확인) 구성 방법
- web-vitals 라이브러리로 성능을 모니터링하는 방법
- Angular에서 사용자 정의 오류 처리기를 만드는 방법
- 프로덕션 환경에서 디버깅을 위해 소스 맵을 로드하는 방법
- 효과적인 경고 전략
- 모니터링 대시보드를 설정하는 방법
- 가장 널리 사용되는 모니터링 도구 비교
1. 프런트엔드 모니터링의 세 가지 범주
프런트엔드 모니터링은 각각 목표와 도구가 있는 세 가지 보완적인 영역으로 나뉩니다. 구체적:
모니터링 카테고리
| 범주 | 모니터링 대상 | 일반적인 도구 | 우선 사항 |
|---|---|---|---|
| 오류 추적 | JavaScript 예외, HTTP 오류, 애플리케이션 충돌 | 센트리, 버그스내그, 롤바 | 비판 |
| 성능 모니터링 | 핵심 웹 바이탈, 로드 시간, 네트워크 지표 | 웹바이탈, SpeedCurve, Datadog RUM | 높은 |
| 사용자 행동 | 탐색, 상호작용, 전환 유입경로 | 구글애널리틱스, 믹스패널, 진폭 | 평균 |
2. Sentry를 이용한 오류 추적
보초 프론트엔드 세계에서 가장 많이 사용되는 오류 추적 플랫폼입니다. JavaScript 예외를 자동으로 포착하여 브라우저 컨텍스트로 강화합니다. 유형별로 그룹화하여 디버깅을 크게 촉진합니다.
기본 설치 및 구성
# Installare il pacchetto Sentry per Angular
npm install @sentry/angular @sentry/browser
# Oppure con il wizard automatico
npx @sentry/wizard@latest -i angular
// main.ts - Inizializzazione di Sentry
import * as Sentry from '@sentry/angular';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
// Inizializzare Sentry PRIMA del bootstrap dell'applicazione
Sentry.init({
dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0',
environment: 'production',
release: 'my-app@1.0.0',
// Tracciamento delle performance
tracesSampleRate: 0.2, // 20% delle transazioni
// Replay delle sessioni con errori
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
// Filtrare errori non rilevanti
ignoreErrors: [
'ResizeObserver loop limit exceeded',
'Non-Error exception captured',
/Loading chunk [\d]+ failed/,
],
// Informazioni aggiuntive per ogni evento
beforeSend(event) {
// Rimuovere dati sensibili
if (event.request?.cookies) {
delete event.request.cookies;
}
return event;
},
});
bootstrapApplication(AppComponent, appConfig);
공급자를 사용한 각도 구성
// app.config.ts - Registrare i provider Sentry
import { ApplicationConfig, ErrorHandler } from '@angular/core';
import { provideRouter, Router } from '@angular/router';
import * as Sentry from '@sentry/angular';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
// Error Handler personalizzato che invia a Sentry
{
provide: ErrorHandler,
useValue: Sentry.createErrorHandler({
showDialog: false, // Non mostrare il dialog di Sentry agli utenti
logErrors: true, // Loggare anche nella console
}),
},
// Tracciamento delle navigazioni Angular
{
provide: Sentry.TraceService,
deps: [Router],
},
],
};
샘플링 속도에 주의하세요
Il tracesSampleRate Sentry로 전송되는 트랜잭션 수를 결정합니다.
값 1.0 (100%) 개발에는 유용하지만 생산에는 비용이 너무 많이 듭니다.
교통량이 많은 곳. 다음으로 시작 0.1 o 0.2 필요하다면 늘리세요.
에 대한 replaysOnErrorSampleRate, 유지하다 1.0 캡처
오류가 발생하면 항상 세션입니다.
3. Angular의 사용자 정의 오류 처리기
오류를 보다 세부적으로 제어하기 위해 사용자 정의 오류 처리기를 만들 수 있습니다. 이는 Sentry와 추가 로직을 결합합니다.
// custom-error-handler.ts
import { ErrorHandler, Injectable, inject } from '@angular/core';
import * as Sentry from '@sentry/angular';
@Injectable()
export class CustomErrorHandler implements ErrorHandler {
handleError(error: unknown): void {
// Classificare l'errore
const errorInfo = this.classifyError(error);
// Inviare a Sentry con contesto aggiuntivo
Sentry.withScope((scope) => {
scope.setTag('error.type', errorInfo.type);
scope.setTag('error.severity', errorInfo.severity);
scope.setLevel(errorInfo.severity as Sentry.SeverityLevel);
if (errorInfo.type === 'http') {
scope.setContext('http', {
status: (error as any)?.status,
url: (error as any)?.url,
});
}
Sentry.captureException(error);
});
// Loggare nella console in development
console.error('[CustomErrorHandler]', error);
}
private classifyError(error: unknown): { type: string; severity: string } {
if (error instanceof TypeError) {
return { type: 'type-error', severity: 'error' };
}
if ((error as any)?.status !== undefined) {
const status = (error as any).status;
if (status >= 500) return { type: 'http', severity: 'error' };
if (status >= 400) return { type: 'http', severity: 'warning' };
}
if (error instanceof Error && error.message?.includes('ChunkLoadError')) {
return { type: 'chunk-load', severity: 'warning' };
}
return { type: 'unknown', severity: 'error' };
}
}
4. 실제 사용자 모니터링(RUM)
Il 실제 사용자 모니터링 사용자가 인식하는 성능을 측정합니다.
시뮬레이션된 환경이 아닌 실제 환경입니다. 도서관 web-vitals Google과 표준
사용자의 브라우저에서 직접 Core Web Vital을 수집합니다.
# Installare web-vitals
npm install web-vitals
// web-vitals.service.ts
import { Injectable } from '@angular/core';
import { onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals';
import type { Metric } from 'web-vitals';
@Injectable({ providedIn: 'root' })
export class WebVitalsService {
initializeTracking(): void {
// Core Web Vitals
onCLS(this.reportMetric.bind(this));
onINP(this.reportMetric.bind(this));
onLCP(this.reportMetric.bind(this));
// Metriche supplementari
onFCP(this.reportMetric.bind(this));
onTTFB(this.reportMetric.bind(this));
}
private reportMetric(metric: Metric): void {
const data = {
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType,
};
// Inviare a un endpoint di analisi
this.sendToAnalytics(data);
// Inviare a Sentry come breadcrumb
this.sendToSentry(data);
// Log in console per debugging
console.log(`[Web Vital] ${metric.name}: ${metric.value} (${metric.rating})`);
}
private sendToAnalytics(data: Record<string, unknown>): void {
// Inviare a Google Analytics
if (typeof gtag !== 'undefined') {
gtag('event', data['name'] as string, {
value: Math.round(data['value'] as number),
event_category: 'Web Vitals',
event_label: data['rating'] as string,
non_interaction: true,
});
}
}
private sendToSentry(data: Record<string, unknown>): void {
import('@sentry/angular').then((Sentry) => {
Sentry.addBreadcrumb({
category: 'web-vital',
message: `${data['name']}: ${data['value']} (${data['rating']})`,
level: data['rating'] === 'poor' ? 'warning' : 'info',
data: data,
});
});
}
}
루트 구성 요소에서 추적 초기화
// app.component.ts
import { Component, OnInit, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { WebVitalsService } from './services/web-vitals.service';
@Component({
selector: 'app-root',
template: '<router-outlet />',
})
export class AppComponent implements OnInit {
private webVitals = inject(WebVitalsService);
private platformId = inject(PLATFORM_ID);
ngOnInit(): void {
// Web Vitals funziona solo nel browser, non in SSR
if (isPlatformBrowser(this.platformId)) {
this.webVitals.initializeTracking();
}
}
}
5. 종합 모니터링(가동시간 확인)
Il 합성 모니터링 정기적으로 자동 검사를 수행하여 사이트에 접속할 수 있고 작동하는지 확인하세요. RUM과 달리 사용자 트래픽에서: 아무도 사이트를 방문하지 않는 오전 3시에도 문제를 감지합니다.
# GitHub Actions per uptime monitoring
name: Uptime Check
on:
schedule:
- cron: '*/15 * * * *' # Ogni 15 minuti
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Check site availability
run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://example.com)
RESPONSE_TIME=$(curl -s -o /dev/null -w "%{time_total}" https://example.com)
echo "Status: $STATUS"
echo "Response time: ${RESPONSE_TIME}s"
if [ "$STATUS" != "200" ]; then
echo "ALERT: Site returned status $STATUS"
exit 1
fi
# Controllare il tempo di risposta
THRESHOLD="3.0"
if (( $(echo "$RESPONSE_TIME > $THRESHOLD" | bc -l) )); then
echo "WARNING: Response time ${RESPONSE_TIME}s exceeds threshold ${THRESHOLD}s"
fi
종합 모니터링 서비스
- 가동 시간로봇: 모니터 50대 무료 요금제, 5분 간격으로 확인
- 핑덤: 통합 RUM 분석을 통한 고급 모니터링
- 체크하게: Playwright를 통한 브라우저 확인, 복잡한 흐름에 이상적
- 더 나은 가동 시간: 공개 상태 페이지를 통한 사고 관리
- Google 클라우드 모니터링: GCP 생태계와 통합된 가동시간 확인
6. 디버깅을 위한 소스 맵 업로드
프로덕션 환경에서는 JavaScript 코드가 축소되어 읽을 수 없습니다. 그만큼 소스 맵 Sentry가 변수 이름과 숫자가 포함된 원본 스택 추적을 표시하도록 허용 소스 코드 라인. Sentry에 업로드하는 것이 중요하지만 공개적으로 제공하지 않는 것이 좋습니다.
# Upload delle source map nella pipeline CI/CD
- name: Build with source maps
run: npx ng build --configuration=production --source-map
- name: Upload source maps to Sentry
uses: getsentry/action-release@v1
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: my-organization
SENTRY_PROJECT: my-angular-app
with:
environment: production
version: ${{ github.sha }}
sourcemaps: './dist/my-app/browser'
url_prefix: '~/'
- name: Remove source maps before deploy
run: find dist/my-app/browser -name "*.map" -delete
프로덕션에서 소스 맵을 제공하지 마십시오
소스 맵에는 애플리케이션의 전체 소스 코드가 포함되어 있습니다. 공개적으로 제공 누구든지 비즈니스 로직을 읽고, 취약점을 찾아낼 수 있으며, 내부 아키텍처를 이해합니다. Sentry에 업로드한 다음 디렉터리에서 삭제하세요. 배포.
7. 경고 전략
경보가 울리지 않고 사이렌이 울리지 않는 화재 경보기와 같은 모니터링 시스템입니다. 그러나 경고가 너무 많으면 팀에서 알림을 무시하는 "경고 피로"가 발생합니다. 핵심은 지능형 임계값 정의:
권장 경고 수준
| 수준 | 트리거 | 채널 | 답변 |
|---|---|---|---|
| 심각(P0) | 사이트가 완전히 다운되었습니다. 오류 > 50% | SMS + Slack + PagerDuty | 즉시(15분 이내) |
| 높음(P1) | 중요한 기능이 손상됨, 오류율 > 10% | Slack + 이메일 | 1시간 이내 |
| 중간(P2) | 성능 저하, 심각하지 않은 오류 > 5% | 느슨하게 | 4시간 이내 |
| 낮음(P3) | 경고, 부정적인 추세의 측정항목 | 주간 보고서 | 다음 스프린트 |
Sentry의 경고 구성
Configurazione Alert Sentry:
Alert 1: Spike di Errori
Condizione: Quando il numero di eventi > 100 in 1 ora
Azione: Notifica Slack #alerts + email team lead
Frequenza: Max 1 notifica ogni 30 minuti
Alert 2: Nuovo Errore in Produzione
Condizione: Prima occorrenza di un nuovo issue
Azione: Notifica Slack #errors
Frequenza: Ogni nuovo issue
Alert 3: Regressione
Condizione: Issue precedentemente risolto riappare
Azione: Notifica Slack #alerts + assegna all'ultimo resolver
Frequenza: Ogni regressione
Alert 4: Performance Budget
Condizione: LCP p95 > 4 secondi
Azione: Notifica Slack #performance
Frequenza: Max 1 notifica ogni 2 ore
8. 모니터링 대시보드
효과적인 대시보드는 애플리케이션 상태에 대한 즉각적인 개요를 제공합니다. 포함해야 할 필수 측정항목은 다음과 같습니다.
필수 대시보드
+----------------------------------------------------------+
| Application Health |
+----------------------------------------------------------+
| |
| Uptime: 99.97% Error Rate: 0.3% Avg LCP: 1.8s |
| |
+----------------------------------------------------------+
| Core Web Vitals (p75) |
| +--------+ +--------+ +--------+ |
| | LCP | | INP | | CLS | |
| | 1.8s | | 120ms | | 0.05 | |
| | (good) | | (good) | | (good) | |
| +--------+ +--------+ +--------+ |
+----------------------------------------------------------+
| Errori Ultimi 7 Giorni | Top Errori |
| +-----------------------------+ | 1. TypeError: null |
| | Mon Tue Wed Thu Fri Sat Sun | | 2. ChunkLoadError |
| | 5 3 8 2 12 1 0 | | 3. HttpError 500 |
| +-----------------------------+ | 4. NetworkError |
+----------------------------------------------------------+
9. 모니터링 도구 비교
모니터링 도구 비교
| 기구 | 유형 | 무료 플랜 | 강점 | 이상적인 대상 |
|---|---|---|---|---|
| 보초 | 오류 추적 + 성능 | 5,000개 이벤트/월 | 오류 그룹화, 소스 맵 | 모든 프로젝트 |
| 데이터독 럼 | 풀스택 모니터링 | 제한된 | 프런트엔드-백엔드 상관관계 | 기업 |
| 로그로켓 | 세션 재생 + 오류 추적 | 세션 1,000개/월 | 세션의 비디오 재생 | UX 디버깅 |
| 뉴렐릭 브라우저 | RUM + 오류 추적 | 100GB/월 | 맞춤형 대시보드 | 운영팀 |
| 버그스내그 | 오류 추적 | 이벤트 7,500개/월 | 릴리스 추적 안정성 | 모바일 + 웹 |
10. 구현 완료: 체크리스트
다음은 Angular 프로젝트에서 모니터링을 구현하기 위한 전체 체크리스트입니다.
// monitoring-setup.ts - Configurazione centralizzata
import { Injectable, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Injectable({ providedIn: 'root' })
export class MonitoringService {
private platformId = inject(PLATFORM_ID);
initialize(): void {
if (!isPlatformBrowser(this.platformId)) return;
// 1. Error Tracking (Sentry già configurato nel main.ts)
// 2. Web Vitals
this.setupWebVitals();
// 3. Custom error boundary per errori non gestiti
this.setupGlobalErrorHandlers();
// 4. Network monitoring
this.setupNetworkMonitoring();
}
private async setupWebVitals(): Promise<void> {
const { onCLS, onINP, onLCP, onFCP, onTTFB } = await import('web-vitals');
const report = (metric: any) => {
console.log(`[Vital] ${metric.name}: ${metric.value}`);
};
onCLS(report);
onINP(report);
onLCP(report);
onFCP(report);
onTTFB(report);
}
private setupGlobalErrorHandlers(): void {
// Catturare promise non gestite
window.addEventListener('unhandledrejection', (event) => {
console.error('[Unhandled Rejection]', event.reason);
});
// Catturare errori di caricamento risorse
window.addEventListener('error', (event) => {
if (event.target instanceof HTMLScriptElement) {
console.error('[Script Load Error]', event);
}
}, true);
}
private setupNetworkMonitoring(): void {
// Monitorare i cambiamenti di connessione
const connection = (navigator as any).connection;
if (connection) {
connection.addEventListener('change', () => {
console.log('[Network]', {
effectiveType: connection.effectiveType,
downlink: connection.downlink,
rtt: connection.rtt,
});
});
}
}
}
가동 모니터링 체크리스트
- 사용자 정의 오류 처리기로 구성되고 실행되는 Sentry
- Sentry에 업로드된 소스 맵(및 배포에서 제거됨)
- Google Analytics로 전송하여 활성 상태인 웹 바이탈 추적
- 가동시간 모니터링 구성(5분마다 확인)
- 심각한 오류에 대해 구성된 경고(Slack, 이메일)
- 필수 지표로 생성된 대시보드
- Sentry 토큰 순환 계획이 구성되었습니다.
- 문서화된 사고 대응 절차
- SSR 호환성 검증됨(isPlatformBrowser)
- 트래픽 볼륨에 최적화된 샘플링 속도
결론
이 기사로 시리즈를 마무리합니다. 프런트엔드 개발자를 위한 DevOps. GitHub Actions를 사용하여 CI/CD 파이프라인을 구축하는 것부터 Docker를 사용한 컨테이너화, Firebase를 사용한 자동 배포, 비밀 관리, Lighthouse CI를 통한 성능 모니터링, 생산 모니터링까지 센트리 및 웹 바이탈.
프로덕션 모니터링으로 DevOps 루프가 종료됩니다. 실제 사용자로부터 데이터를 수집합니다. 이는 개발 결정, 버그 수정 우선 순위 및 성능 최적화를 촉진합니다. 이는 선택적인 구성 요소가 아니며 안정적인 애플리케이션을 구축하고 개선하기 위한 기반입니다. 사용자 경험을 지속적으로 개선합니다.
프런트엔드 DevOps 및 점진적 반복의 성공 열쇠: 구현하지 않음 단번에 기본(CI/CD + 오류 추적)부터 시작하여 추가 점진적으로 고급 구성 요소(성능 예산, 종합 모니터링, 구조화된 경고) 프로젝트와 팀이 성숙해짐에 따라







