## 추가된 파일 - utils/errors.js: 표준화된 커스텀 에러 클래스 - AppError, ValidationError, AuthenticationError - ForbiddenError, NotFoundError, ConflictError - DatabaseError, ExternalApiError, TimeoutError - utils/logger.js: 통합 로깅 유틸리티 - 로그 레벨별 관리 (ERROR, WARN, INFO, DEBUG) - 콘솔 및 파일 로깅 지원 - HTTP 요청/DB 쿼리 전용 로거 ## 개선된 파일 - middlewares/errorHandler.js: 에러 핸들러 개선 - 새로운 AppError 클래스 사용 - logger 통합 - asyncHandler 및 notFoundHandler 추가 ## 다음 단계 - config 파일들 생성 (cors, security) - activityLogger 미들웨어 생성 - userController 인라인 코드 분리 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
198 lines
4.2 KiB
JavaScript
198 lines
4.2 KiB
JavaScript
/**
|
||
* 로깅 유틸리티
|
||
*
|
||
* 애플리케이션 전체에서 사용하는 통합 로거
|
||
*
|
||
* @author TK-FB-Project
|
||
* @since 2025-12-11
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
/**
|
||
* 로그 레벨 정의
|
||
*/
|
||
const LogLevel = {
|
||
ERROR: 'ERROR',
|
||
WARN: 'WARN',
|
||
INFO: 'INFO',
|
||
DEBUG: 'DEBUG'
|
||
};
|
||
|
||
/**
|
||
* 로그 레벨별 이모지
|
||
*/
|
||
const LogEmoji = {
|
||
ERROR: '❌',
|
||
WARN: '⚠️',
|
||
INFO: 'ℹ️',
|
||
DEBUG: '🔍'
|
||
};
|
||
|
||
/**
|
||
* 로그 레벨별 색상 (콘솔)
|
||
*/
|
||
const LogColor = {
|
||
ERROR: '\x1b[31m', // Red
|
||
WARN: '\x1b[33m', // Yellow
|
||
INFO: '\x1b[36m', // Cyan
|
||
DEBUG: '\x1b[90m', // Gray
|
||
RESET: '\x1b[0m'
|
||
};
|
||
|
||
class Logger {
|
||
constructor() {
|
||
this.logDir = path.join(__dirname, '../logs');
|
||
this.logFile = path.join(this.logDir, 'app.log');
|
||
this.errorFile = path.join(this.logDir, 'error.log');
|
||
this.ensureLogDirectory();
|
||
}
|
||
|
||
/**
|
||
* 로그 디렉토리 생성
|
||
*/
|
||
ensureLogDirectory() {
|
||
if (!fs.existsSync(this.logDir)) {
|
||
fs.mkdirSync(this.logDir, { recursive: true });
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 타임스탬프 생성
|
||
*/
|
||
getTimestamp() {
|
||
return new Date().toISOString();
|
||
}
|
||
|
||
/**
|
||
* 로그 포맷팅
|
||
*/
|
||
formatLog(level, message, context = {}) {
|
||
const timestamp = this.getTimestamp();
|
||
const emoji = LogEmoji[level] || '';
|
||
const contextStr = Object.keys(context).length > 0
|
||
? `\n Context: ${JSON.stringify(context, null, 2)}`
|
||
: '';
|
||
|
||
return `[${timestamp}] [${level}] ${emoji} ${message}${contextStr}`;
|
||
}
|
||
|
||
/**
|
||
* 콘솔에 컬러 로그 출력
|
||
*/
|
||
logToConsole(level, message, context = {}) {
|
||
const color = LogColor[level] || LogColor.RESET;
|
||
const formattedLog = this.formatLog(level, message, context);
|
||
|
||
if (level === LogLevel.ERROR) {
|
||
console.error(`${color}${formattedLog}${LogColor.RESET}`);
|
||
} else if (level === LogLevel.WARN) {
|
||
console.warn(`${color}${formattedLog}${LogColor.RESET}`);
|
||
} else {
|
||
console.log(`${color}${formattedLog}${LogColor.RESET}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 파일에 로그 기록
|
||
*/
|
||
logToFile(level, message, context = {}) {
|
||
const formattedLog = this.formatLog(level, message, context);
|
||
const logEntry = `${formattedLog}\n`;
|
||
|
||
try {
|
||
// 모든 로그를 app.log에 기록
|
||
fs.appendFileSync(this.logFile, logEntry, 'utf8');
|
||
|
||
// 에러는 error.log에도 기록
|
||
if (level === LogLevel.ERROR) {
|
||
fs.appendFileSync(this.errorFile, logEntry, 'utf8');
|
||
}
|
||
} catch (err) {
|
||
console.error('로그 파일 기록 실패:', err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 로그 기록 메인 함수
|
||
*/
|
||
log(level, message, context = {}) {
|
||
// 개발 환경에서는 콘솔에 출력
|
||
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV !== 'production') {
|
||
this.logToConsole(level, message, context);
|
||
}
|
||
|
||
// 프로덕션에서는 파일에만 기록
|
||
if (process.env.NODE_ENV === 'production') {
|
||
this.logToFile(level, message, context);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 에러 로그
|
||
*/
|
||
error(message, context = {}) {
|
||
this.log(LogLevel.ERROR, message, context);
|
||
}
|
||
|
||
/**
|
||
* 경고 로그
|
||
*/
|
||
warn(message, context = {}) {
|
||
this.log(LogLevel.WARN, message, context);
|
||
}
|
||
|
||
/**
|
||
* 정보 로그
|
||
*/
|
||
info(message, context = {}) {
|
||
this.log(LogLevel.INFO, message, context);
|
||
}
|
||
|
||
/**
|
||
* 디버그 로그
|
||
*/
|
||
debug(message, context = {}) {
|
||
// DEBUG 로그는 개발 환경에서만 출력
|
||
if (process.env.NODE_ENV === 'development') {
|
||
this.log(LogLevel.DEBUG, message, context);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* HTTP 요청 로그
|
||
*/
|
||
http(method, url, statusCode, duration, user = 'anonymous') {
|
||
const level = statusCode >= 400 ? LogLevel.ERROR : LogLevel.INFO;
|
||
const message = `${method} ${url} - ${statusCode} (${duration}ms)`;
|
||
const context = {
|
||
method,
|
||
url,
|
||
statusCode,
|
||
duration,
|
||
user
|
||
};
|
||
|
||
this.log(level, message, context);
|
||
}
|
||
|
||
/**
|
||
* 데이터베이스 쿼리 로그
|
||
*/
|
||
query(sql, params = [], duration = 0) {
|
||
if (process.env.NODE_ENV === 'development') {
|
||
this.debug('DB Query', {
|
||
sql,
|
||
params,
|
||
duration: `${duration}ms`
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// 싱글톤 인스턴스 생성 및 내보내기
|
||
const logger = new Logger();
|
||
|
||
module.exports = logger;
|