refactor(backend): 로거 통일

- utils/logger.js 생성 (createLogger)
- 서비스 레이어: logger 유틸리티 사용
- 라우트 레이어: fastify.log 사용
- console.error/log → 구조화된 로깅으로 변경

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
caadiq 2026-01-21 14:20:32 +09:00
parent f719fd9259
commit 7b227a6c56
9 changed files with 98 additions and 44 deletions

View file

@ -195,7 +195,7 @@ export default async function photosRoutes(fastify) {
reply.raw.end();
} catch (error) {
await connection.rollback();
console.error('사진 업로드 오류:', error);
fastify.log.error(`사진 업로드 오류: ${error.message}`);
reply.raw.write(`data: ${JSON.stringify({ error: '사진 업로드 중 오류가 발생했습니다.' })}\n\n`);
reply.raw.end();
} finally {

View file

@ -261,6 +261,6 @@ async function saveSearchQueryAsync(fastify, query) {
const service = new SuggestionService(fastify.db, fastify.redis);
await service.saveSearchQuery(query);
} catch (err) {
console.error('[Search] 검색어 저장 실패:', err.message);
fastify.log.error(`[Search] 검색어 저장 실패: ${err.message}`);
}
}

View file

@ -15,7 +15,7 @@ export default async function suggestionsRoutes(fastify) {
suggestionService = new SuggestionService(db, redis);
// 비동기 초기화 (형태소 분석기 로드)
suggestionService.initialize().catch(err => {
console.error('[Suggestions] 서비스 초기화 실패:', err.message);
fastify.log.error(`[Suggestions] 서비스 초기화 실패: ${err.message}`);
});
}
@ -186,7 +186,7 @@ export default async function suggestionsRoutes(fastify) {
return { message: '사전이 저장되었습니다.' };
} catch (error) {
console.error('[Suggestions] 사전 저장 오류:', error.message);
fastify.log.error(`[Suggestions] 사전 저장 오류: ${error.message}`);
return reply.code(500).send({ error: '사전 저장 중 오류가 발생했습니다.' });
}
});

View file

@ -1,6 +1,9 @@
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';
import config from '../config/index.js';
import { createLogger } from '../utils/logger.js';
const logger = createLogger('S3');
// S3 클라이언트 생성
const s3Client = new S3Client({
@ -61,7 +64,7 @@ async function deleteFromS3(key) {
Key: key,
}));
} catch (err) {
console.error(`S3 삭제 오류 (${key}):`, err.message);
logger.error(`삭제 오류 (${key}): ${err.message}`);
}
}

View file

@ -7,8 +7,10 @@
*/
import Inko from 'inko';
import config, { CATEGORY_IDS } from '../../config/index.js';
import { createLogger } from '../../utils/logger.js';
const inko = new Inko();
const logger = createLogger('Meilisearch');
const INDEX_NAME = 'schedules';
const MIN_SCORE = config.meilisearch.minScore;
@ -111,7 +113,7 @@ export async function searchSchedules(meilisearch, db, query, options = {}) {
hasMore: offset + paginatedHits.length < total,
};
} catch (err) {
console.error('[Meilisearch] 검색 오류:', err.message);
logger.error(`검색 오류: ${err.message}`);
return { hits: [], total: 0, offset: 0, limit, hasMore: false };
}
}
@ -183,9 +185,9 @@ export async function addOrUpdateSchedule(meilisearch, schedule) {
};
await index.addDocuments([document]);
console.log(`[Meilisearch] 일정 추가/업데이트: ${schedule.id}`);
logger.info(`일정 추가/업데이트: ${schedule.id}`);
} catch (err) {
console.error('[Meilisearch] 문서 추가 오류:', err.message);
logger.error(`문서 추가 오류: ${err.message}`);
}
}
@ -196,9 +198,9 @@ export async function deleteSchedule(meilisearch, scheduleId) {
try {
const index = meilisearch.index(INDEX_NAME);
await index.deleteDocument(scheduleId);
console.log(`[Meilisearch] 일정 삭제: ${scheduleId}`);
logger.info(`일정 삭제: ${scheduleId}`);
} catch (err) {
console.error('[Meilisearch] 문서 삭제 오류:', err.message);
logger.error(`문서 삭제 오류: ${err.message}`);
}
}
@ -249,11 +251,11 @@ export async function syncAllSchedules(meilisearch, db) {
// 일괄 추가
await index.addDocuments(documents);
console.log(`[Meilisearch] ${documents.length}개 일정 동기화 완료`);
logger.info(`${documents.length}개 일정 동기화 완료`);
return documents.length;
} catch (err) {
console.error('[Meilisearch] 동기화 오류:', err.message);
logger.error(`동기화 오류: ${err.message}`);
return 0;
}
}

View file

@ -8,8 +8,10 @@
import Inko from 'inko';
import { extractNouns, initMorpheme, isReady } from './morpheme.js';
import { getChosung, isChosungOnly, isChosungMatch } from './chosung.js';
import { createLogger } from '../../utils/logger.js';
const inko = new Inko();
const logger = createLogger('Suggestion');
// 설정
const CONFIG = {
@ -42,9 +44,9 @@ export class SuggestionService {
async initialize() {
try {
await initMorpheme();
console.log('[Suggestion] 서비스 초기화 완료');
logger.info('서비스 초기화 완료');
} catch (error) {
console.error('[Suggestion] 서비스 초기화 실패:', error.message);
logger.error(`서비스 초기화 실패: ${error.message}`);
}
}
@ -79,7 +81,7 @@ export class SuggestionService {
if (this.isEnglishOnly(normalizedQuery)) {
const korean = this.convertEnglishToKorean(normalizedQuery);
if (korean) {
console.log(`[Suggestion] 한글 변환: "${normalizedQuery}" → "${korean}"`);
logger.debug(`한글 변환: "${normalizedQuery}" → "${korean}"`);
normalizedQuery = korean;
}
}
@ -131,9 +133,9 @@ export class SuggestionService {
}
}
console.log(`[Suggestion] 저장: "${normalizedQuery}" → 명사: [${nouns.join(', ')}]`);
logger.debug(`저장: "${normalizedQuery}" → 명사: [${nouns.join(', ')}]`);
} catch (error) {
console.error('[Suggestion] 저장 오류:', error.message);
logger.error(`저장 오류: ${error.message}`);
}
}
@ -171,7 +173,7 @@ export class SuggestionService {
return await this.getPrefixSuggestions(searchQuery.trim(), koreanQuery?.trim(), limit);
}
} catch (error) {
console.error('[Suggestion] 조회 오류:', error.message);
logger.error(`조회 오류: ${error.message}`);
return [];
}
}
@ -200,7 +202,7 @@ export class SuggestionService {
return rows.map(r => `${prefix} ${r.word2}`);
} catch (error) {
console.error('[Suggestion] Bi-gram 조회 오류:', error.message);
logger.error(`Bi-gram 조회 오류: ${error.message}`);
return [];
}
}
@ -236,7 +238,7 @@ export class SuggestionService {
return rows.map(r => r.query);
} catch (error) {
console.error('[Suggestion] Prefix 조회 오류:', error.message);
logger.error(`Prefix 조회 오류: ${error.message}`);
return [];
}
}
@ -258,7 +260,7 @@ export class SuggestionService {
return rows.map(r => r.word);
} catch (error) {
console.error('[Suggestion] 초성 검색 오류:', error.message);
logger.error(`초성 검색 오류: ${error.message}`);
return [];
}
}
@ -293,7 +295,7 @@ export class SuggestionService {
return result;
} catch (error) {
console.error('[Suggestion] 인기 검색어 조회 오류:', error.message);
logger.error(`인기 검색어 조회 오류: ${error.message}`);
return [];
}
}

View file

@ -5,6 +5,9 @@
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { createLogger } from '../../utils/logger.js';
const logger = createLogger('Morpheme');
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@ -47,7 +50,7 @@ export async function initMorpheme() {
initPromise = (async () => {
try {
console.log('[Morpheme] kiwi-nlp 초기화 시작...');
logger.info('kiwi-nlp 초기화 시작...');
// kiwi-nlp 동적 import (ESM)
const { KiwiBuilder } = await import('kiwi-nlp');
@ -69,7 +72,7 @@ export async function initMorpheme() {
try {
modelFiles[filename] = new Uint8Array(readFileSync(filepath));
} catch (err) {
console.warn(`[Morpheme] 모델 파일 로드 실패: ${filename}`);
logger.warn(`모델 파일 로드 실패: ${filename}`);
}
}
@ -78,18 +81,18 @@ export async function initMorpheme() {
try {
modelFiles[USER_DICT] = new Uint8Array(readFileSync(userDictPath));
userDicts = [USER_DICT];
console.log('[Morpheme] 사용자 사전 로드 완료');
logger.info('사용자 사전 로드 완료');
} catch (err) {
console.warn('[Morpheme] 사용자 사전 없음, 기본 사전만 사용');
logger.warn('사용자 사전 없음, 기본 사전만 사용');
}
// Kiwi 인스턴스 생성
kiwi = await builder.build({ modelFiles, userDicts });
isInitialized = true;
console.log('[Morpheme] kiwi-nlp 초기화 완료');
logger.info('kiwi-nlp 초기화 완료');
} catch (error) {
console.error('[Morpheme] 초기화 실패:', error.message);
logger.error(`초기화 실패: ${error.message}`);
// 초기화 실패해도 서비스는 계속 동작 (fallback 사용)
}
})();
@ -114,7 +117,7 @@ export async function extractNouns(text) {
// kiwi가 초기화되지 않았으면 fallback
if (!kiwi) {
console.warn('[Morpheme] kiwi 미초기화, fallback 사용');
logger.warn('kiwi 미초기화, fallback 사용');
return fallbackExtract(text);
}
@ -141,7 +144,7 @@ export async function extractNouns(text) {
return nouns.length > 0 ? nouns : fallbackExtract(text);
} catch (error) {
console.error('[Morpheme] 형태소 분석 오류:', error.message);
logger.error(`형태소 분석 오류: ${error.message}`);
return fallbackExtract(text);
}
}
@ -167,12 +170,12 @@ export function isReady() {
* 형태소 분석기 리로드 (사전 변경 호출)
*/
export async function reloadMorpheme() {
console.log('[Morpheme] 리로드 시작...');
logger.info('리로드 시작...');
isInitialized = false;
kiwi = null;
initPromise = null;
await initMorpheme();
console.log('[Morpheme] 리로드 완료');
logger.info('리로드 완료');
}
/**

View file

@ -0,0 +1,43 @@
/**
* 로거 유틸리티
* 서비스 레이어에서 사용할 있는 간단한 로깅 유틸리티
*/
const PREFIX = {
info: '[INFO]',
warn: '[WARN]',
error: '[ERROR]',
debug: '[DEBUG]',
};
function formatMessage(level, context, message) {
const timestamp = new Date().toISOString();
return `${timestamp} ${PREFIX[level]} [${context}] ${message}`;
}
/**
* 로거 생성
* @param {string} context - 로깅 컨텍스트 (: 'Meilisearch', 'Suggestions')
* @returns {object} 로거 객체
*/
export function createLogger(context) {
return {
info: (message, ...args) => {
console.log(formatMessage('info', context, message), ...args);
},
warn: (message, ...args) => {
console.warn(formatMessage('warn', context, message), ...args);
},
error: (message, ...args) => {
console.error(formatMessage('error', context, message), ...args);
},
debug: (message, ...args) => {
if (process.env.DEBUG) {
console.debug(formatMessage('debug', context, message), ...args);
}
},
};
}
// 기본 로거 (컨텍스트 없음)
export default createLogger('App');

View file

@ -86,18 +86,19 @@
---
### 10단계: 로거 통일
- [ ] `src/utils/logger.js` 생성
- [ ] 모든 `console.error/log` → logger 사용
### 10단계: 로거 통일 ✅ 완료
- [x] `src/utils/logger.js` 생성
- [x] 모든 `console.error/log` → logger 또는 fastify.log 사용
**대상 파일 (30개 이상):**
- `services/image.js:61`
- `services/meilisearch/index.js:112, 188, 201, 256`
- `services/suggestions/index.js:47, 136, 174, 203, 239, 261, 296`
- `services/suggestions/morpheme.js:92, 144`
- `routes/albums/photos.js:199`
- `routes/schedules/index.js:264`
- `routes/schedules/suggestions.js:18, 190`
**수정된 파일:**
- `src/utils/logger.js` - 로거 유틸리티 생성 (createLogger)
- `src/services/image.js` - logger 사용
- `src/services/meilisearch/index.js` - logger 사용
- `src/services/suggestions/index.js` - logger 사용
- `src/services/suggestions/morpheme.js` - logger 사용
- `src/routes/albums/photos.js` - fastify.log 사용
- `src/routes/schedules/index.js` - fastify.log 사용
- `src/routes/schedules/suggestions.js` - fastify.log 사용
---
@ -120,7 +121,7 @@
| 7단계 | 순차→병렬 쿼리 | ✅ 완료 |
| 8단계 | meilisearch 카테고리 ID | ✅ 완료 |
| 9단계 | 응답 형식 통일 | ✅ 완료 |
| 10단계 | 로거 통일 | 대기 |
| 10단계 | 로거 통일 | ✅ 완료 |
| 11단계 | 대형 핸들러 분리 | 대기 |
---