Files
telegram-bot-workers/docs/logger-guide.md
kappa c0e47482c4 feat(phase-5-3): 모니터링 강화
logger.ts, metrics.ts, /api/metrics 추가
Version: e3bcb4ae
2026-01-19 16:43:36 +09:00

592 lines
13 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Logger 사용 가이드
구조화된 로깅 유틸리티 (`src/utils/logger.ts`) 사용 방법 안내
## 목차
1. [개요](#개요)
2. [기본 사용법](#기본-사용법)
3. [로그 레벨](#로그-레벨)
4. [성능 측정](#성능-측정)
5. [사용자 컨텍스트](#사용자-컨텍스트)
6. [출력 형식](#출력-형식)
7. [마이그레이션 가이드](#마이그레이션-가이드)
8. [Best Practices](#best-practices)
---
## 개요
### 주요 특징
-**JSON 기반 구조화 로그**: 프로덕션 환경에서 로그 수집 시스템 연동
-**환경별 자동 전환**: 개발(읽기 쉬운 포맷) ↔ 프로덕션(JSON)
-**타입 안전성**: TypeScript strict mode 지원
-**성능 측정**: 내장 타이머로 실행 시간 자동 계산
-**에러 안전성**: 로깅 실패가 메인 로직 중단시키지 않음
-**사용자 컨텍스트**: 사용자 ID 자동 포함
### 로그 레벨
| 레벨 | 우선순위 | 용도 | 예시 |
|------|---------|------|------|
| `DEBUG` | 0 | 디버깅 정보 (개발 전용) | 함수 호출, 파라미터 값 |
| `INFO` | 1 | 일반 정보 | 요청 처리, 성공 응답 |
| `WARN` | 2 | 경고 (주의 필요) | API 지연, 폴백 사용 |
| `ERROR` | 3 | 에러 (복구 가능) | API 실패, 재시도 |
| `FATAL` | 4 | 치명적 에러 | 시스템 장애, DB 연결 실패 |
---
## 기본 사용법
### 1. 로거 생성
```typescript
import { createLogger } from './utils/logger';
// 서비스별 로거 생성
const logger = createLogger('openai');
```
### 2. 로그 출력
```typescript
// 일반 정보
logger.info('AI 응답 생성 시작');
// 컨텍스트 포함
logger.info('API 호출 완료', {
model: 'gpt-4o-mini',
tokensUsed: 1000,
});
// 경고
logger.warn('API 응답 지연', {
endpoint: '/chat/completions',
responseTime: 3500,
});
```
### 3. 에러 처리
```typescript
try {
await callOpenAI();
} catch (error) {
logger.error('OpenAI API 호출 실패', error as Error, {
model: 'gpt-4o-mini',
retryCount: 3,
});
}
```
---
## 로그 레벨
### DEBUG (개발 전용)
```typescript
import { createDebugLogger } from './utils/logger';
const logger = createDebugLogger('debug-service');
logger.debug('함수 호출', {
functionName: 'processData',
arguments: [1, 2, 3],
});
```
### INFO (기본)
```typescript
logger.info('메시지 수신', {
userId: '123456789',
messageType: 'text',
length: 42,
});
```
### WARN (주의 필요)
```typescript
logger.warn('Circuit breaker 경고', {
failureCount: 2,
threshold: 3,
});
```
### ERROR (복구 가능)
```typescript
try {
await apiCall();
} catch (error) {
logger.error('API 호출 실패', error as Error, {
endpoint: '/api/data',
retryAttempt: 2,
});
}
```
### FATAL (치명적)
```typescript
logger.fatal('데이터베이스 연결 실패', error as Error, {
database: 'telegram-conversations',
connectionString: 'D1',
});
```
---
## 성능 측정
### 기본 사용
```typescript
const endTimer = logger.startTimer('데이터 처리 완료', {
recordCount: 10000,
});
await processData();
endTimer(); // duration이 자동으로 로그에 포함됨
```
### OpenAI API 호출 예시
```typescript
const logger = createLogger('openai');
async function generateResponse() {
const end = logger.startTimer('AI 응답 생성 완료');
try {
const response = await callOpenAI();
end(); // 성공 시 duration 로그
return response;
} catch (error) {
logger.error('AI 응답 생성 실패', error as Error);
throw error;
}
}
```
### 출력 예시
```
[2026-01-19T16:45:23.123Z] INFO [openai] AI 응답 생성 완료 {"model":"gpt-4o-mini"} (1234ms)
```
---
## 사용자 컨텍스트
### 사용자 ID 자동 포함
```typescript
const logger = createLogger('telegram');
// 사용자 로거 생성 (userId 자동 포함)
const userLogger = logger.withUser('821596605');
userLogger.info('메시지 수신', {
messageType: 'text',
length: 42,
});
userLogger.info('AI 응답 전송', {
responseLength: 150,
});
```
### 출력 예시
```json
{
"timestamp": "2026-01-19T16:45:23.123Z",
"level": "INFO",
"service": "telegram",
"message": "메시지 수신",
"userId": "821596605",
"context": {
"messageType": "text",
"length": 42
}
}
```
---
## 출력 형식
### 개발 환경 (human-readable)
```
[2026-01-19T16:45:23.123Z] INFO [openai] AI 응답 생성 시작 {"userId":"123","model":"gpt-4"}
⚠️ [2026-01-19T16:45:25.456Z] WARN [openai] API 응답 지연 {"responseTime":3500}
❌ [2026-01-19T16:45:27.789Z] ERROR [openai] API 호출 실패 {"retryCount":3}
Error: Error: Connection timeout
Stack: Error: Connection timeout
at callOpenAI (openai-service.ts:45:11)
```
### 프로덕션 (JSON)
```json
{
"timestamp": "2026-01-19T16:45:23.123Z",
"level": "INFO",
"service": "openai",
"message": "AI 응답 생성 시작",
"context": {
"userId": "123",
"model": "gpt-4"
}
}
```
```json
{
"timestamp": "2026-01-19T16:45:27.789Z",
"level": "ERROR",
"service": "openai",
"message": "API 호출 실패",
"context": {
"retryCount": 3
},
"error": {
"name": "Error",
"message": "Connection timeout",
"stack": "Error: Connection timeout\n at callOpenAI (openai-service.ts:45:11)"
}
}
```
---
## 마이그레이션 가이드
### 기존 코드
```typescript
// ❌ 기존: console.log
console.log('[OpenAI] AI 응답 생성 시작');
console.log('[OpenAI] tool_calls:', JSON.stringify(toolCalls));
console.error('[OpenAI] API 호출 실패:', error);
```
### 새 코드
```typescript
// ✅ 신규: createLogger
const logger = createLogger('openai');
logger.info('AI 응답 생성 시작');
logger.debug('tool_calls 수신', { toolCalls });
logger.error('API 호출 실패', error as Error, { model: 'gpt-4o-mini' });
```
### 서비스별 변환 예시
#### openai-service.ts
```typescript
// Before
console.log('[OpenAI] tool_calls:', assistantMessage.tool_calls);
console.error('[OpenAI] Circuit breaker open:', error.message);
// After
const logger = createLogger('openai');
logger.debug('tool_calls 수신', { toolCalls: assistantMessage.tool_calls });
logger.error('Circuit breaker 열림', error as Error);
```
#### deposit-agent.ts
```typescript
// Before
console.log(`[DepositAgent] Function call: ${funcName}`, funcArgs);
console.error('[DepositAgent] Error:', error);
// After
const logger = createLogger('deposit');
logger.info('Function call 실행', { functionName: funcName, arguments: funcArgs });
logger.error('예치금 처리 실패', error as Error);
```
#### services/notification.ts
```typescript
// Before
console.log('[Notification] 관리자 ID가 설정되지 않아 알림을 건너뜁니다.');
console.error('[Notification] 알림 전송 중 오류 발생:', error);
// After
const logger = createLogger('notification');
logger.warn('관리자 ID 미설정', { notificationType: type });
logger.error('알림 전송 실패', error as Error, { type, service: details.service });
```
---
## Best Practices
### 1. 서비스별 로거 생성
```typescript
// ✅ Good: 서비스별 로거
const openaiLogger = createLogger('openai');
const telegramLogger = createLogger('telegram');
const depositLogger = createLogger('deposit');
// ❌ Bad: 로거 재사용
const logger = createLogger('app');
```
### 2. 컨텍스트 정보 포함
```typescript
// ✅ Good: 컨텍스트 포함
logger.info('메시지 수신', {
userId: '123',
messageType: 'text',
length: 42,
});
// ❌ Bad: 메시지에 직접 포함
logger.info(`메시지 수신: userId=123, type=text, length=42`);
```
### 3. 에러 객체 전달
```typescript
// ✅ Good: 에러 객체 전달
try {
await apiCall();
} catch (error) {
logger.error('API 호출 실패', error as Error, { endpoint: '/api/data' });
}
// ❌ Bad: 에러를 문자열로 변환
try {
await apiCall();
} catch (error) {
logger.error(`API 호출 실패: ${error}`);
}
```
### 4. 성능 측정 활용
```typescript
// ✅ Good: 타이머 사용
const end = logger.startTimer('데이터 처리 완료');
await processData();
end();
// ❌ Bad: 수동 계산
const start = Date.now();
await processData();
logger.info(`데이터 처리 완료 (${Date.now() - start}ms)`);
```
### 5. 민감 정보 제외
```typescript
// ✅ Good: 민감 정보 제외
logger.info('API 호출', {
endpoint: '/api/data',
method: 'POST',
});
// ❌ Bad: API 키, 비밀번호 등 포함
logger.info('API 호출', {
endpoint: '/api/data',
apiKey: env.OPENAI_API_KEY, // ❌ 절대 금지
password: userPassword, // ❌ 절대 금지
});
```
### 6. 로그 레벨 적절히 사용
```typescript
// ✅ Good: 레벨에 맞는 로그
logger.debug('디버그 정보', { details }); // 개발 전용
logger.info('일반 정보', { status: 'ok' }); // 정상 동작
logger.warn('경고', { delayMs: 3500 }); // 주의 필요
logger.error('에러', error, { context }); // 복구 가능 에러
logger.fatal('치명적 에러', error); // 시스템 장애
// ❌ Bad: 모든 로그를 info로
logger.info('디버그 정보'); // ❌ debug 사용
logger.info('경고'); // ❌ warn 사용
logger.info('에러 발생'); // ❌ error 사용
```
---
## 환경 설정
### 환경 변수
`wrangler.toml` 또는 환경 변수로 설정:
```toml
[vars]
ENVIRONMENT = "production" # 또는 "development"
```
### 로그 레벨 제어
```typescript
// 프로덕션: INFO 레벨 이상만 출력
const logger = createLogger('service', env);
// 개발: DEBUG 레벨부터 모든 로그 출력
const logger = createDebugLogger('service');
```
---
## 실제 사용 예시
### OpenAI 서비스
```typescript
const logger = createLogger('openai', env);
export async function generateOpenAIResponse(
env: Env,
userMessage: string,
systemPrompt: string
): Promise<string> {
const userLogger = logger.withUser(telegramUserId || 'unknown');
const end = logger.startTimer('AI 응답 생성 완료');
try {
userLogger.info('AI 응답 생성 시작', {
messageLength: userMessage.length,
model: 'gpt-4o-mini',
});
const response = await callOpenAI(apiKey, messages, selectedTools);
userLogger.info('Function Calling 실행', {
toolName: toolCall.function.name,
arguments: toolCall.function.arguments,
});
end();
return response;
} catch (error) {
if (error instanceof CircuitBreakerError) {
userLogger.error('Circuit breaker 열림', error, {
service: 'openai',
});
} else if (error instanceof RetryError) {
userLogger.error('재시도 실패', error, {
maxRetries: 3,
});
} else {
userLogger.error('AI 응답 생성 실패', error as Error);
}
throw error;
}
}
```
### 예치금 서비스
```typescript
const logger = createLogger('deposit', env);
export async function matchPendingDeposit(
db: D1Database,
notification: BankNotification,
env: Env
): Promise<boolean> {
logger.info('입금 매칭 시작', {
depositorName: notification.depositorName,
amount: notification.amount,
bank: notification.bankName,
});
const end = logger.startTimer('입금 매칭 완료');
try {
const result = await db.prepare(/* ... */).first();
if (!result) {
logger.info('매칭되는 pending 거래 없음');
return false;
}
logger.info('매칭 발견', {
transactionId: pendingTx.id,
userId: pendingTx.user_id,
});
await db.batch([/* ... */]);
logger.info('매칭 완료', {
transactionId: pendingTx.id,
newBalance: currentBalance + notification.amount,
});
end();
return true;
} catch (error) {
logger.error('DB 업데이트 실패', error as Error, {
transactionId: pendingTx?.id,
});
throw error;
}
}
```
---
## 문제 해결
### Q: 로그가 출력되지 않습니다
**A:** 로그 레벨 확인:
```typescript
// DEBUG 레벨은 개발 환경에서만 출력됨
const logger = createDebugLogger('service'); // DEBUG 포함
const logger = createLogger('service'); // INFO 이상만
```
### Q: 프로덕션에서 JSON이 아닌 텍스트 로그가 출력됩니다
**A:** 환경 변수 확인:
```typescript
// wrangler.toml
[vars]
ENVIRONMENT = "production"
```
### Q: 에러 스택이 출력되지 않습니다
**A:** 에러 객체를 전달하세요:
```typescript
// ✅ Good
logger.error('실패', error as Error);
// ❌ Bad
logger.error('실패', undefined, { error: error.message });
```
---
## 추가 자료
- **구현 파일**: `/src/utils/logger.ts`
- **테스트 예시**: `/src/utils/__test__/logger.test.ts`
- **CLAUDE.md**: 개발자 가이드 (Code Style & Conventions 섹션)