メインコンテンツにスキップ

📊 에러 로깅과 모니터링

📖 정의

**에러 로깅(Error Logging)**은 애플리케이션에서 발생하는 에러와 중요한 이벤트를 기록하는 것이고, **모니터링(Monitoring)**은 실시간으로 애플리케이션 상태를 추적하는 것입니다. console.log는 개발 환경에서만 유용하며, 프로덕션에서는 구조화된 로깅, 에러 추적 도구(Sentry), 로그 레벨, 알림 시스템이 필수입니다.

🎯 비유로 이해하기

병원의 환자 모니터링

에러 로깅을 병원 시스템에 비유하면:

console.log = 간호사의 메모
├─ 개인 수첩에 기록
├─ 체계적이지 않음
├─ 퇴근하면 못 봄
└─ 응급 상황 대응 어려움

로깅 시스템 = 전자 의료 기록
├─ 모든 정보 중앙 저장
├─ 시간순 정렬
├─ 검색 가능
└─ 여러 의사가 동시 접근

에러 모니터링 = 환자 모니터
├─ 실시간 생체 신호
├─ 이상 시 즉시 알림
├─ 대시보드로 한눈에
└─ 24시간 감시

로그 레벨 = 응급도 분류
├─ ERROR = 응급 (즉시 대응)
├─ WARN = 주의 (관찰 필요)
├─ INFO = 정상 (기록용)
└─ DEBUG = 상세 (진단용)

비행기 블랙박스

개발 환경 = 지상 테스트
└─ console.log로 충분

프로덕션 환경 = 실제 비행
├─ 블랙박스 (로깅 시스템)
│ └─ 모든 이벤트 기록
├─ 계기판 (모니터링 대시보드)
│ └─ 실시간 상태 확인
└─ 관제탑 (알림 시스템)
└─ 문제 발생 시 즉시 통보

사고 발생 시:
1. 블랙박스 확인 (로그 분석)
2. 원인 파악 (스택 트레이스)
3. 재발 방지 (모니터링 강화)

⚙️ 작동 원리

1. 로그 레벨 (Log Levels)

ERROR (높음) - 즉시 대응 필요
├─ 서버 크래시
├─ 데이터베이스 연결 실패
├─ 결제 시스템 오류
└─ 예: "PaymentService: Credit card charge failed"

WARN - 주의 필요
├─ 성능 저하
├─ 디스크 공간 부족
├─ API 응답 느림
└─ 예: "Database: Connection pool near capacity (90%)"

INFO - 정상 작동 기록
├─ 서버 시작/종료
├─ 사용자 로그인
├─ 주요 기능 실행
└─ 예: "Server started on port 3000"

DEBUG - 개발/디버깅용
├─ 함수 호출 추적
├─ 변수 값 확인
├─ 상세한 실행 흐름
└─ 예: "UserService.findById called with id=123"

TRACE (낮음) - 매우 상세
└─ 모든 세부 정보

2. 에러 추적 흐름

애플리케이션에서 에러 발생

에러 캐치 (try-catch)

에러 정보 수집
├─ 에러 메시지
├─ 스택 트레이스
├─ 사용자 정보
├─ 요청 정보 (URL, params)
├─ 환경 정보 (브라우저, OS)
└─ 시간

로깅 시스템으로 전송
├─ Sentry
├─ LogRocket
└─ CloudWatch

분석 및 알림
├─ 에러 그룹화
├─ 빈도 계산
├─ 심각도 판단
└─ Slack/이메일 알림

개발자 대응
└─ 원인 파악 및 수정

3. Sentry 작동 원리

클라이언트 (브라우저/서버)
├─ Sentry SDK 초기화
├─ 자동 에러 캐치
└─ 에러 발생 시 전송

Sentry 서버
├─ 에러 수신
├─ 소스맵 적용 (압축 해제)
├─ 스택 트레이스 정리
├─ 중복 제거 (같은 에러 그룹화)
├─ 영향받은 사용자 수 계산
└─ 알림 규칙 확인

대시보드
├─ 에러 목록
├─ 상세 정보
├─ 타임라인
├─ 영향 범위
└─ 해결 상태

알림 (조건 충족 시)
├─ Slack
├─ 이메일
├─ Discord
└─ PagerDuty

💡 실제 예시

Step 1: 기본 로깅 시스템 구축

# 프로젝트 생성
mkdir logging-demo
cd logging-demo
npm init -y

# Winston 로깅 라이브러리 설치
npm install winston

# Express 및 기타 패키지
npm install express dotenv
// logger.js
// Winston으로 구조화된 로깅 시스템 구축

const winston = require('winston');
const path = require('path');

// 로그 포맷 정의
const logFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);

// 개발 환경용 포맷 (사람이 읽기 쉽게)
const consoleFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(({ timestamp, level, message, ...meta }) => {
let msg = `${timestamp} [${level}]: ${message}`;
if (Object.keys(meta).length > 0) {
msg += ` ${JSON.stringify(meta)}`;
}
return msg;
})
);

// Logger 생성
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: logFormat,
transports: [
// 에러 로그 파일
new winston.transports.File({
filename: path.join('logs', 'error.log'),
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5
}),

// 전체 로그 파일
new winston.transports.File({
filename: path.join('logs', 'combined.log'),
maxsize: 5242880,
maxFiles: 5
})
]
});

// 개발 환경에서는 콘솔에도 출력
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: consoleFormat
}));
}

// 헬퍼 함수
logger.logRequest = (req, res, duration) => {
logger.info('HTTP Request', {
method: req.method,
url: req.originalUrl,
status: res.statusCode,
duration: `${duration}ms`,
ip: req.ip,
userAgent: req.get('user-agent')
});
};

logger.logError = (error, req = null) => {
const errorInfo = {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
};

if (req) {
errorInfo.request = {
method: req.method,
url: req.originalUrl,
headers: req.headers,
body: req.body,
query: req.query,
params: req.params,
ip: req.ip
};
}

logger.error('Application Error', errorInfo);
};

module.exports = logger;
// server.js
require('dotenv').config();
const express = require('express');
const logger = require('./logger');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

// 요청 로깅 미들웨어
app.use((req, res, next) => {
const start = Date.now();

// 응답이 끝나면 로그 기록
res.on('finish', () => {
const duration = Date.now() - start;
logger.logRequest(req, res, duration);
});

next();
});

// 정상 라우트
app.get('/', (req, res) => {
logger.info('Home page accessed');
res.json({ message: 'Welcome to Logging Demo' });
});

// 의도적으로 에러 발생
app.get('/error', (req, res) => {
logger.warn('Error endpoint accessed - this will throw an error');

// 에러 발생!
throw new Error('This is a test error');
});

// 데이터베이스 시뮬레이션
app.get('/users/:id', (req, res) => {
const userId = req.params.id;

logger.debug('Fetching user', { userId });

// 사용자 조회 (시뮬레이션)
if (userId === '1') {
logger.info('User found', { userId, username: 'john_doe' });
res.json({ id: 1, username: 'john_doe' });
} else {
logger.warn('User not found', { userId });
res.status(404).json({ error: 'User not found' });
}
});

// 느린 API 시뮬레이션
app.get('/slow', async (req, res) => {
const delay = 3000;
logger.warn('Slow API called', { delay });

await new Promise(resolve => setTimeout(resolve, delay));

res.json({ message: 'This was slow' });
});

// 404 핸들러
app.use((req, res) => {
logger.warn('404 Not Found', {
method: req.method,
url: req.originalUrl
});

res.status(404).json({ error: 'Not Found' });
});

// 에러 핸들러
app.use((err, req, res, next) => {
// 에러 로깅
logger.logError(err, req);

// 클라이언트 응답
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined
});
});

app.listen(PORT, () => {
logger.info('Server started', {
port: PORT,
environment: process.env.NODE_ENV || 'development'
});
});

module.exports = app;
# .env
PORT=3000
NODE_ENV=development
LOG_LEVEL=debug

# logs 디렉토리 생성
mkdir logs

# .gitignore에 추가
echo "logs/" >> .gitignore

# 서버 실행
node server.js

# 출력 예시:
# 2024-01-15 10:00:00 [info]: Server started {"port":"3000","environment":"development"}

# 테스트
curl http://localhost:3000
# 2024-01-15 10:00:05 [info]: Home page accessed
# 2024-01-15 10:00:05 [info]: HTTP Request {"method":"GET","url":"/","status":200,"duration":"5ms",...}

curl http://localhost:3000/error
# 2024-01-15 10:00:10 [warn]: Error endpoint accessed - this will throw an error
# 2024-01-15 10:00:10 [error]: Application Error {"message":"This is a test error","stack":"Error: This is a test error\n at..."}

# 로그 파일 확인
tail -f logs/combined.log
tail -f logs/error.log

Step 2: Sentry 통합 (프로덕션 에러 추적)

# Sentry 패키지 설치
npm install @sentry/node @sentry/tracing
// sentry.js
const Sentry = require('@sentry/node');
const Tracing = require('@sentry/tracing');

function initSentry(app) {
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV || 'development',
tracesSampleRate: 1.0, // 성능 모니터링: 100% 요청 추적

// Release 버전 (git commit hash 사용 권장)
release: process.env.APP_VERSION || '1.0.0',

// Express 통합
integrations: [
new Sentry.Integrations.Http({ tracing: true }),
new Tracing.Integrations.Express({ app })
]
});

return Sentry;
}

module.exports = { initSentry };
// server.js (Sentry 추가)
require('dotenv').config();
const express = require('express');
const logger = require('./logger');
const { initSentry } = require('./sentry');

const app = express();
const Sentry = initSentry(app);

// Sentry 미들웨어 (최상단에 배치)
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());

app.use(express.json());

// ... 기존 라우트 ...

// 에러 핸들러 (Sentry 에러 핸들러 먼저)
app.use(Sentry.Handlers.errorHandler());

// 커스텀 에러 핸들러
app.use((err, req, res, next) => {
// Winston으로도 로깅
logger.logError(err, req);

// Sentry에 추가 컨텍스트 전송
Sentry.withScope((scope) => {
scope.setUser({
id: req.user?.id,
username: req.user?.username,
email: req.user?.email
});

scope.setExtra('requestBody', req.body);
scope.setExtra('requestQuery', req.query);

Sentry.captureException(err);
});

res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined
});
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
logger.info('Server started with Sentry monitoring', {
port: PORT,
environment: process.env.NODE_ENV
});
});
# .env에 Sentry DSN 추가
cat >> .env << 'EOF'
SENTRY_DSN=https://abc123xyz456@o123456.ingest.sentry.io/7890123
APP_VERSION=1.0.0
EOF

# Sentry DSN 얻기:
# 1. https://sentry.io 가입
# 2. 새 프로젝트 생성 (Node.js)
# 3. DSN 복사
// 에러 발생 시 Sentry 대시보드에서 확인 가능:
// - 에러 메시지와 스택 트레이스
// - 발생 시간과 빈도
// - 영향받은 사용자 수
// - 요청 정보 (URL, headers, body)
// - 사용자 정보 (로그인한 경우)
// - 환경 정보 (Node 버전, OS 등)

Step 3: 프론트엔드 에러 추적 (React)

# React 프로젝트에서
npm install @sentry/react @sentry/tracing
// src/sentry.js
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';

export function initSentry() {
Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
environment: process.env.NODE_ENV,
release: process.env.REACT_APP_VERSION || '1.0.0',

integrations: [
new BrowserTracing(),
new Sentry.Replay({
maskAllText: true,
blockAllMedia: true
})
],

// 성능 모니터링
tracesSampleRate: 1.0,

// 세션 재생 (에러 발생 시 화면 녹화)
replaysSessionSampleRate: 0.1, // 10% 세션 녹화
replaysOnErrorSampleRate: 1.0, // 에러 발생 시 100% 녹화

// 에러 필터링 (무시할 에러)
beforeSend(event, hint) {
// 크롬 익스텐션 에러 무시
if (event.exception?.values?.[0]?.value?.includes('chrome-extension')) {
return null;
}

// 네트워크 에러 필터링
if (hint.originalException?.message?.includes('NetworkError')) {
return null;
}

return event;
}
});
}
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import * as Sentry from '@sentry/react';
import { initSentry } from './sentry';
import App from './App';

// Sentry 초기화
initSentry();

const root = ReactDOM.createRoot(document.getElementById('root'));

// ErrorBoundary로 앱 감싸기
root.render(
<React.StrictMode>
<Sentry.ErrorBoundary
fallback={({ error, componentStack, resetError }) => (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h1>, 문제가 발생했습니다! 😢</h1>
<p>죄송합니다. 오류가 발생했습니다.</p>
<button onClick={resetError}>다시 시도</button>

{process.env.NODE_ENV === 'development' && (
<details style={{ marginTop: '20px', textAlign: 'left' }}>
<summary>에러 상세 정보</summary>
<pre>{error.toString()}</pre>
<pre>{componentStack}</pre>
</details>
)}
</div>
)}
showDialog
>
<App />
</Sentry.ErrorBoundary>
</React.StrictMode>
);
// src/App.js
import React, { useState } from 'react';
import * as Sentry from '@sentry/react';

function App() {
const [count, setCount] = useState(0);

// 의도적인 에러
const causeError = () => {
throw new Error('This is a test error from React');
};

// API 호출 에러
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('API request failed');
const data = await response.json();
console.log(data);
} catch (error) {
// Sentry로 전송
Sentry.captureException(error);
alert('데이터를 불러오는 데 실패했습니다.');
}
};

// 커스텀 에러 보고
const reportIssue = () => {
Sentry.captureMessage('사용자가 이슈를 보고했습니다', 'warning');
};

// 사용자 정보 설정 (로그인 시)
const setUser = () => {
Sentry.setUser({
id: '12345',
username: 'john_doe',
email: 'john@example.com'
});
};

// Breadcrumb (에러 발생 전 사용자 행동 추적)
const handleClick = () => {
Sentry.addBreadcrumb({
category: 'user-action',
message: 'User clicked button',
level: 'info'
});

setCount(count + 1);
};

return (
<div style={{ padding: '40px' }}>
<h1>Sentry Error Tracking Demo</h1>

<div style={{ margin: '20px 0' }}>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment (with breadcrumb)</button>
</div>

<div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap' }}>
<button onClick={causeError}>
Throw Error
</button>

<button onClick={fetchData}>
API Error
</button>

<button onClick={reportIssue}>
Report Issue
</button>

<button onClick={setUser}>
Set User
</button>
</div>

{/* 성능 측정 */}
<Sentry.Profiler name="MyComponent">
<ExpensiveComponent />
</Sentry.Profiler>
</div>
);
}

function ExpensiveComponent() {
// 무거운 계산
const result = Array.from({ length: 1000000 }, (_, i) => i * 2)
.reduce((a, b) => a + b, 0);

return <div>Result: {result}</div>;
}

export default Sentry.withProfiler(App);
# .env
REACT_APP_SENTRY_DSN=https://xyz789@sentry.io/123456
REACT_APP_VERSION=1.0.0

# 빌드 및 배포
npm run build

# Sentry에서 확인 가능:
# - JavaScript 에러
# - 네트워크 에러
# - 사용자 행동 (Breadcrumbs)
# - 세션 재생 (에러 발생 시 화면 녹화)
# - 성능 메트릭

Step 4: 로그 분석 및 알림 설정

// alerting.js
// Slack 알림 통합

const axios = require('axios');
const logger = require('./logger');

class AlertManager {
constructor() {
this.slackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
this.errorThreshold = 10; // 10분에 10개 에러 시 알림
this.errorCount = 0;
this.resetInterval = 10 * 60 * 1000; // 10분

// 주기적으로 카운터 리셋
setInterval(() => {
this.errorCount = 0;
}, this.resetInterval);
}

async sendSlackAlert(message, severity = 'error') {
if (!this.slackWebhookUrl) {
logger.warn('Slack webhook URL not configured');
return;
}

const color = {
error: '#FF0000',
warning: '#FFA500',
info: '#0000FF'
}[severity] || '#808080';

const payload = {
attachments: [
{
color: color,
title: `🚨 ${severity.toUpperCase()}: Application Alert`,
text: message,
footer: 'Logging System',
ts: Math.floor(Date.now() / 1000)
}
]
};

try {
await axios.post(this.slackWebhookUrl, payload);
logger.info('Slack alert sent', { severity, message });
} catch (error) {
logger.error('Failed to send Slack alert', { error: error.message });
}
}

onError(error, context = {}) {
this.errorCount++;

// 임계값 초과 시 알림
if (this.errorCount >= this.errorThreshold) {
const message = `Error threshold exceeded: ${this.errorCount} errors in ${this.resetInterval / 60000} minutes\n\nLatest error: ${error.message}`;
this.sendSlackAlert(message, 'error');

// 카운터 리셋 (스팸 방지)
this.errorCount = 0;
}
}

onSlowResponse(url, duration) {
if (duration > 3000) { // 3초 이상
const message = `Slow API detected:\nURL: ${url}\nDuration: ${duration}ms`;
this.sendSlackAlert(message, 'warning');
}
}

onServerStart(port) {
const message = `Server started successfully on port ${port}`;
this.sendSlackAlert(message, 'info');
}
}

module.exports = new AlertManager();
// server.js에 알림 추가
const alertManager = require('./alerting');

// 서버 시작 시 알림
app.listen(PORT, () => {
logger.info('Server started', { port: PORT });
alertManager.onServerStart(PORT);
});

// 에러 핸들러에 알림 추가
app.use((err, req, res, next) => {
logger.logError(err, req);
alertManager.onError(err, { url: req.originalUrl });

res.status(500).json({ error: 'Internal Server Error' });
});

// 느린 응답 모니터링
app.use((req, res, next) => {
const start = Date.now();

res.on('finish', () => {
const duration = Date.now() - start;
alertManager.onSlowResponse(req.originalUrl, duration);
});

next();
});
# .env에 Slack Webhook URL 추가
cat >> .env << 'EOF'
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX
EOF

# Slack Webhook 설정:
# 1. Slack 워크스페이스 → Apps
# 2. "Incoming Webhooks" 검색 및 추가
# 3. 채널 선택 (#alerts)
# 4. Webhook URL 복사

Step 5: 대시보드 및 시각화

# ELK Stack (Elasticsearch, Logstash, Kibana) 간단한 대안
# Winston → File → 분석 도구

# 또는 클라우드 로깅 서비스:
# - AWS CloudWatch
# - Google Cloud Logging
# - Datadog
# - New Relic

# 간단한 로그 분석 스크립트
cat > analyze-logs.js << 'EOF'
const fs = require('fs');
const readline = require('readline');

async function analyzeLogs(filePath) {
const stats = {
total: 0,
byLevel: {},
byUrl: {},
errors: []
};

const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});

for await (const line of rl) {
try {
const log = JSON.parse(line);
stats.total++;

// 레벨별 집계
stats.byLevel[log.level] = (stats.byLevel[log.level] || 0) + 1;

// URL별 집계
if (log.url) {
stats.byUrl[log.url] = (stats.byUrl[log.url] || 0) + 1;
}

// 에러 수집
if (log.level === 'error') {
stats.errors.push({
message: log.message,
timestamp: log.timestamp
});
}
} catch (err) {
// JSON 파싱 실패 무시
}
}

return stats;
}

// 실행
analyzeLogs('./logs/combined.log').then(stats => {
console.log('📊 Log Analysis Report\n');
console.log(`Total Logs: ${stats.total}\n`);

console.log('By Level:');
Object.entries(stats.byLevel).forEach(([level, count]) => {
console.log(` ${level}: ${count}`);
});

console.log('\nTop URLs:');
Object.entries(stats.byUrl)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([url, count]) => {
console.log(` ${url}: ${count}`);
});

console.log(`\nTotal Errors: ${stats.errors.length}`);
if (stats.errors.length > 0) {
console.log('\nRecent Errors:');
stats.errors.slice(-5).forEach(err => {
console.log(` [${err.timestamp}] ${err.message}`);
});
}
});
EOF

node analyze-logs.js

🤔 자주 묻는 질문

Q1. console.log와 로깅 라이브러리의 차이는?

A:

// console.log (개발용)
console.log('User logged in');
console.log('Error:', error);

// 문제점:
// ❌ 로그 레벨 없음 (모두 같은 중요도)
// ❌ 구조화되지 않음
// ❌ 파일 저장 안 됨
// ❌ 검색/필터링 어려움
// ❌ 프로덕션에서 성능 저하
// ❌ 중앙 집중식 관리 불가

// Winston (프로덕션용)
logger.info('User logged in', { userId: 123 });
logger.error('Login failed', { error: error.message, userId: 123 });

// 장점:
// ✅ 로그 레벨 (error, warn, info, debug)
// ✅ 구조화된 데이터 (JSON)
// ✅ 파일/데이터베이스 저장
// ✅ 검색/필터링 쉬움
// ✅ 환경별 설정 (dev/prod)
// ✅ 로그 회전 (파일 크기 관리)
// ✅ 여러 전송 대상 (파일, 콘솔, 외부 서비스)

// 비교:

// 개발 환경
if (process.env.NODE_ENV === 'development') {
console.log('Quick debug'); // OK
}

// 프로덕션 환경
logger.info('User action', { action: 'login', userId: 123 }); // 권장

// 규칙:
// - 개발: console.log 사용 가능 (빠른 디버깅)
// - 프로덕션: 반드시 로깅 라이브러리 사용
// - 모든 console.log는 배포 전 제거 또는 logger로 변경

Q2. 어떤 정보를 로깅해야 하나요?

A:

// ✅ 로깅해야 할 것

// 1. 시스템 이벤트
logger.info('Server started', { port: 3000, environment: 'production' });
logger.info('Database connected', { host: 'mongodb.example.com' });
logger.warn('Database connection lost, reconnecting...');

// 2. 사용자 행동 (비즈니스 로직)
logger.info('User logged in', { userId: 123, method: 'email' });
logger.info('Order placed', { userId: 123, orderId: 456, amount: 99.99 });
logger.info('Payment processed', { orderId: 456, paymentMethod: 'card' });

// 3. 에러 및 예외
logger.error('Payment failed', {
error: error.message,
orderId: 456,
userId: 123,
stack: error.stack
});

// 4. 성능 관련
logger.warn('Slow query detected', {
query: 'SELECT * FROM users',
duration: 5000, // ms
threshold: 1000
});

// 5. 보안 이벤트
logger.warn('Multiple failed login attempts', {
userId: 123,
ip: '192.168.1.1',
attempts: 5
});

logger.error('Unauthorized access attempt', {
url: '/admin',
ip: '192.168.1.1'
});

// 6. 외부 API 호출
logger.info('External API called', {
service: 'PaymentGateway',
endpoint: '/charge',
duration: 500
});

// ❌ 로깅하면 안 되는 것

// 1. 민감한 정보
// ❌ logger.info('User login', { password: '12345' });
// ✅ logger.info('User login', { userId: 123 });

// ❌ logger.info('Payment', { cardNumber: '1234-5678-9012-3456' });
// ✅ logger.info('Payment', { cardLast4: '3456' });

// 2. 개인 정보
// ❌ logger.info('User data', { ssn: '123-45-6789' });
// ✅ logger.info('User data', { userId: 123 });

// 3. 너무 많은 로그 (성능 저하)
// ❌ 반복문 안에서 매번 로깅
for (let i = 0; i < 1000000; i++) {
logger.debug('Processing item', { i }); // 너무 많음!
}

// ✅ 요약만 로깅
logger.info('Processed items', { count: 1000000, duration: 5000 });

// 민감한 정보 마스킹 헬퍼
function maskSensitive(data) {
const masked = { ...data };

if (masked.password) masked.password = '***';
if (masked.cardNumber) masked.cardNumber = masked.cardNumber.slice(-4).padStart(16, '*');
if (masked.email) masked.email = masked.email.replace(/(.{2}).*(@.*)/, '$1***$2');

return masked;
}

logger.info('User data', maskSensitive({ email: 'john@example.com', password: 'secret' }));
// Output: { email: 'jo***@example.com', password: '***' }

Q3. 로그 파일이 너무 커지는데 어떻게 관리하나요?

A:

// Winston 로그 로테이션 설정

const winston = require('winston');
require('winston-daily-rotate-file');

// 1. 일별 로그 파일
const dailyRotateTransport = new winston.transports.DailyRotateFile({
filename: 'logs/application-%DATE%.log',
datePattern: 'YYYY-MM-DD',
maxSize: '20m', // 파일 크기 제한
maxFiles: '14d', // 14일 보관
zippedArchive: true // 압축 저장
});

// 2. 크기 기반 로테이션
const logger = winston.createLogger({
transports: [
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5, // 5개 파일 유지
tailable: true // 최신 로그를 파일명에 번호 없이 저장
})
]
});

// 3. 수동 로그 정리 스크립트
// cleanup-logs.js
const fs = require('fs');
const path = require('path');

function cleanupOldLogs(directory, daysToKeep = 7) {
const now = Date.now();
const maxAge = daysToKeep * 24 * 60 * 60 * 1000;

fs.readdirSync(directory).forEach(file => {
const filePath = path.join(directory, file);
const stats = fs.statSync(filePath);

if (now - stats.mtime.getTime() > maxAge) {
fs.unlinkSync(filePath);
console.log(`Deleted old log: ${file}`);
}
});
}

cleanupOldLogs('./logs', 7);

// 4. Cron job 설정 (Linux/Mac)
// 매일 자정에 오래된 로그 삭제
// crontab -e
// 0 0 * * * node /path/to/cleanup-logs.js

// 5. 로그 레벨 조정 (프로덕션)
// .env
LOG_LEVEL=info // debug 비활성화 → 로그 양 감소

// 6. 샘플링 (고트래픽 환경)
// 모든 요청을 로깅하지 않고 일부만
let requestCount = 0;

app.use((req, res, next) => {
requestCount++;

// 10번에 1번만 로깅
if (requestCount % 10 === 0) {
logger.info('HTTP Request (sampled)', {
method: req.method,
url: req.url
});
}

next();
});

// 7. 외부 로깅 서비스 사용
// AWS CloudWatch, Datadog 등은 자동으로 관리

Q4. 프로덕션 에러를 어떻게 빠르게 발견하나요?

A:

// 실시간 에러 감지 전략

// 1. Sentry 알림 설정
// Sentry 대시보드:
// - Alerts → New Alert Rule

// 조건 예시:
// "새로운 이슈가 발생하면"
// "1시간에 10명 이상의 사용자가 영향받으면"
// "에러 발생률이 1% 초과하면"

// 알림 채널:
// - Email
// - Slack
// - Discord
// - PagerDuty (온콜)
// - Webhook

// 2. Health Check 엔드포인트
app.get('/health', (req, res) => {
// 데이터베이스 연결 확인
const dbHealthy = checkDatabase();

// 외부 서비스 확인
const servicesHealthy = checkExternalServices();

if (dbHealthy && servicesHealthy) {
res.status(200).json({ status: 'healthy' });
} else {
res.status(503).json({
status: 'unhealthy',
database: dbHealthy,
services: servicesHealthy
});
}
});

// 3. Uptime 모니터링 (외부 서비스)
// - UptimeRobot (무료)
// - Pingdom
// - StatusCake

// 1분마다 /health 엔드포인트 체크
// 다운되면 즉시 알림

// 4. 에러율 모니터링
let totalRequests = 0;
let errorRequests = 0;

app.use((req, res, next) => {
totalRequests++;

res.on('finish', () => {
if (res.statusCode >= 500) {
errorRequests++;

// 에러율 계산
const errorRate = (errorRequests / totalRequests) * 100;

if (errorRate > 5) { // 5% 초과
alertManager.sendSlackAlert(
`🚨 High error rate detected: ${errorRate.toFixed(2)}%`,
'error'
);
}
}
});

next();
});

// 주기적으로 리셋
setInterval(() => {
totalRequests = 0;
errorRequests = 0;
}, 60000); // 1분마다

// 5. 로그 분석 자동화
// - 매시간 로그 분석
// - 이상 패턴 감지
// - 알림 발송

setInterval(async () => {
const recentErrors = await getRecentErrors();

if (recentErrors.length > 100) {
alertManager.sendSlackAlert(
`⚠️ High error volume: ${recentErrors.length} errors in the last hour`,
'warning'
);
}
}, 3600000); // 1시간마다

// 6. 대시보드 활용
// Grafana, Kibana 등으로 실시간 시각화
// - 요청 수
// - 에러율
// - 응답 시간
// - 메모리 사용량

// 7. Slack Bot 통합
// 실시간 에러를 Slack 채널로 전송
// 팀원들이 즉시 확인 가능

Q5. 어떤 로깅 도구를 선택해야 하나요?

A:

// 상황별 추천

// 1. 소규모 프로젝트 (개인/스타트업)
// - Winston (Node.js)
// - 파일 기반 로깅
// - 무료

// 장점: 간단, 빠른 구축
// 단점: 수동 관리 필요

// 2. 중규모 프로젝트 (팀 5-20명)
// - Winston + Sentry
// - Sentry (에러 추적)
// - AWS CloudWatch (로그 중앙화)

// 장점: 에러 자동 알림, 팀 협업
// 비용: Sentry 무료 ~ $26/월

// 3. 대규모 프로젝트 (엔터프라이즈)
// - ELK Stack (Elasticsearch, Logstash, Kibana)
// - Datadog
// - New Relic
// - Splunk

// 장점: 강력한 분석, 시각화, APM
// 비용: $15-$100+/호스트/월

// 4. 서버리스 환경 (AWS Lambda, Vercel)
// - CloudWatch Logs (AWS)
// - Vercel Analytics
// - Sentry (에러만)

// 장점: 인프라 관리 불필요
// 비용: 사용량 기반

// 추천 조합:

// 무료 스타트:
// - Winston (로깅)
// - Sentry Free (에러 추적)
// - UptimeRobot (모니터링)

// 저예산 ($50/월 이하):
// - Winston → AWS CloudWatch
// - Sentry Team ($26/월)
// - Better Uptime

// 프로덕션 권장 ($100-500/월):
// - Datadog ($15/호스트/월)
// - Sentry Team
// - PagerDuty (온콜)

// 선택 기준:
// 1. 팀 크기
// 2. 트래픽
// 3. 예산
// 4. 기술 스택
// 5. 규정 준수 (로그 보관 기간 등)

// 시작 추천:
// 1. Winston으로 시작
// 2. Sentry 무료 추가
// 3. 트래픽 증가 시 CloudWatch/Datadog 도입

🎓 다음 단계

에러 로깅과 모니터링을 마스터했다면, 다음을 학습해보세요:

  1. 환경변수 관리 - Sentry DSN 안전하게 관리하기
  2. REST API 서버 만들기 - API에 로깅 추가하기
  3. CI/CD란? - 자동화된 배포와 모니터링

고급 주제

# APM (Application Performance Monitoring)
- New Relic
- Datadog APM
- Elastic APM

# 분산 추적 (Distributed Tracing)
- Jaeger
- Zipkin
- OpenTelemetry

# 로그 집계 (Log Aggregation)
- ELK Stack
- Graylog
- Loki

# 실시간 모니터링
- Prometheus + Grafana
- InfluxDB + Telegraf

# 인프라 모니터링
- Nagios
- Zabbix
- Icinga

🎬 마무리

에러 로깅과 모니터링의 핵심:

  • 구조화된 로깅: Winston으로 체계적 관리
  • 에러 추적: Sentry로 실시간 감지
  • 로그 레벨: ERROR, WARN, INFO, DEBUG
  • 알림: Slack/Email로 즉시 통보
  • 분석: 패턴 파악 및 개선
  • 보안: 민감 정보 마스킹

"보이지 않는 것은 관리할 수 없습니다." 프로덕션 환경에서는 console.log를 넘어 체계적인 로깅 시스템이 필수입니다. 에러가 발생하기 전에 미리 감지하고, 발생했을 때 빠르게 대응할 수 있도록 모니터링을 구축하세요! 📊✨

사용자가 문제를 신고하기 전에, 여러분이 먼저 알아야 합니다!