로그 설계와 관측성 — 장애 대응 시간을 절반으로 줄인 운영 방식
새벽 3시에 장애 콜 받고 로그 뒤지는데 grep으로 30분 쓴 적 있다.
문제: 로그의 무질서
처음 로깅:
console.log('User login');
console.log('DB error');
console.log('API response sent');
문제:
- 어느 사용자가 로그인했나? 모름
- 어떤 DB 에러? 모름
- 시간이 안 보임
- grep하기 어려움
Solution 1: Structured Logging
JSON 형태로 로그:
// ✅ 좋음
logger.info({
event: 'user_login',
userId: '12345',
email: 'user@example.com',
timestamp: new Date().toISOString(),
duration: 234, // ms
});
// 또는
logger.info('User login', {
userId: '12345',
email: 'user@example.com',
});
로그 포맷:
{"timestamp":"2024-11-10T03:15:45Z","level":"info","event":"user_login","userId":"12345","email":"user@example.com","duration":234}
이제 파싱 가능하고, 검색 가능하다.
로그 레벨 규칙
logger.debug('Entering function processAudio'); // 개발 중에만
logger.info('Call started, callId=123'); // 일반 이벤트
logger.warn('Slow query detected, 2.5s'); // 문제 신호
logger.error('Failed to save user', error); // 에러 (복구 가능)
logger.fatal('Out of memory'); // 중대 에러 (복구 불가)
Correlation ID
한 요청의 모든 로그를 추적:
// Express middleware
app.use((req, res, next) => {
req.correlationId = req.headers['x-correlation-id'] || crypto.randomUUID();
res.setHeader('X-Correlation-ID', req.correlationId);
next();
});
// 로그할 때 항상 correlationId 포함
logger.info('Processing request', {
correlationId: req.correlationId,
userId: req.user.id,
path: req.path,
});
이제 correlationId로 한 요청의 모든 로그를 한 번에 볼 수 있다.
Winston + Loki 설정
import winston from 'winston';
import WinstonLoki from 'winston-loki';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
transports: [
// Console (개발 중)
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
}),
// Loki (프로덕션)
new WinstonLoki({
host: 'http://loki:3100',
labels: { app: 'audio-chat' },
json: true,
}),
],
});
export default logger;
Loki 쿼리 예시
특정 사용자의 모든 로그:
{app="audio-chat"} | json | userId="12345"
에러만 보기:
{app="audio-chat"} | json | level="error"
느린 요청:
{app="audio-chat"} | json | duration > 1000
Grafana Alerting
조건:
에러 로그가 1분 동안 5개 이상이면 alert
// Loki query
sum(count_over_time({app="audio-chat"} | json | level="error" [1m])) > 5
장애 대응 프로세스
Before (30분):
1. SSH 접속 (2분)
2. 로그 파일 찾기 (3분)
3. grep으로 검색 (10분)
4. 원인 파악 (15분)
After (5분):
1. Grafana alert 보기 (30초)
2. Loki에서 correlationId 검색 (1분)
3. 원인 명확 (3분 30초)
핵심 로그 이벤트
음성 채팅 앱이라면:
logger.info('call_started', {
callId: call.id,
participantIds: [user1, user2],
duration: 0,
});
logger.info('call_message_sent', {
callId: call.id,
userId: user.id,
messageLength: message.length,
});
logger.error('call_connection_failed', {
callId: call.id,
userId: user.id,
error: error.message,
errorCode: error.code,
});
결론
로그는 애프리케이션의 눈이다. 구조화된 로그 없으면 장애 대응이 무질서하다.
투자:
- 로깅 라이브러리 선택: 1시간
- 모든 로그 구조화: 1일
- Loki/Grafana 설정: 3시간
- 총 5시간
효과: 장애 대응 시간 50% 단축 (영구적)
가장 좋은 투자다.