[feat]: Item Master 데이터 관리 기능 구현 및 타입 에러 수정
- ItemMasterDataManagement 컴포넌트 구조화 (tabs, dialogs, components 분리) - HierarchyTab 타입 에러 수정 (BOMItem section_id, updated_at 추가) - API 클라이언트 구현 (item-master.ts, 13개 엔드포인트) - ItemMasterContext 구현 (상태 관리 및 데이터 흐름) - 백엔드 요구사항 문서 작성 (CORS 설정, API 스펙 등) - SSR 호환성 수정 (navigator API typeof window 체크) - 미사용 변수 ESLint 에러 해결 - Context 리팩토링 (AuthContext, RootProvider 추가) - API 유틸리티 추가 (error-handler, logger, transformers) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
360
src/lib/api/logger.ts
Normal file
360
src/lib/api/logger.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
// API 호출 로깅 유틸리티
|
||||
// 개발 중 API 요청/응답을 추적하고 디버깅하기 위한 로거
|
||||
|
||||
/**
|
||||
* 로그 레벨
|
||||
*/
|
||||
export enum LogLevel {
|
||||
DEBUG = 'DEBUG',
|
||||
INFO = 'INFO',
|
||||
WARN = 'WARN',
|
||||
ERROR = 'ERROR',
|
||||
}
|
||||
|
||||
/**
|
||||
* API 로그 항목 인터페이스
|
||||
*/
|
||||
interface ApiLogEntry {
|
||||
timestamp: string;
|
||||
level: LogLevel;
|
||||
method: string;
|
||||
url: string;
|
||||
requestData?: any;
|
||||
responseData?: any;
|
||||
statusCode?: number;
|
||||
error?: Error;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Logger 클래스
|
||||
*/
|
||||
class ApiLogger {
|
||||
private enabled: boolean;
|
||||
private logs: ApiLogEntry[] = [];
|
||||
private maxLogs: number = 100;
|
||||
|
||||
constructor() {
|
||||
// 개발 환경에서만 로깅 활성화
|
||||
this.enabled =
|
||||
process.env.NODE_ENV === 'development' ||
|
||||
process.env.NEXT_PUBLIC_API_LOGGING === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* 로깅 활성화 여부 설정
|
||||
*/
|
||||
setEnabled(enabled: boolean) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 최대 개수 설정
|
||||
*/
|
||||
setMaxLogs(max: number) {
|
||||
this.maxLogs = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 요청 시작 로그
|
||||
*/
|
||||
logRequest(method: string, url: string, data?: any): number {
|
||||
if (!this.enabled) return Date.now();
|
||||
|
||||
const startTime = Date.now();
|
||||
const entry: ApiLogEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level: LogLevel.INFO,
|
||||
method,
|
||||
url,
|
||||
requestData: data,
|
||||
};
|
||||
|
||||
console.group(`🚀 API Request: ${method} ${url}`);
|
||||
console.log('⏰ Time:', entry.timestamp);
|
||||
if (data) {
|
||||
console.log('📤 Request Data:', data);
|
||||
}
|
||||
console.groupEnd();
|
||||
|
||||
this.addLog(entry);
|
||||
return startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* API 응답 성공 로그
|
||||
*/
|
||||
logResponse(
|
||||
method: string,
|
||||
url: string,
|
||||
statusCode: number,
|
||||
data: any,
|
||||
startTime: number
|
||||
) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
const entry: ApiLogEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level: LogLevel.INFO,
|
||||
method,
|
||||
url,
|
||||
responseData: data,
|
||||
statusCode,
|
||||
duration,
|
||||
};
|
||||
|
||||
console.group(`✅ API Response: ${method} ${url}`);
|
||||
console.log('⏰ Time:', entry.timestamp);
|
||||
console.log('📊 Status:', statusCode);
|
||||
console.log('⏱️ Duration:', `${duration}ms`);
|
||||
console.log('📥 Response Data:', data);
|
||||
console.groupEnd();
|
||||
|
||||
this.addLog(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 에러 로그
|
||||
*/
|
||||
logError(
|
||||
method: string,
|
||||
url: string,
|
||||
error: Error,
|
||||
statusCode?: number,
|
||||
startTime?: number
|
||||
) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
const duration = startTime ? Date.now() - startTime : undefined;
|
||||
const entry: ApiLogEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level: LogLevel.ERROR,
|
||||
method,
|
||||
url,
|
||||
error,
|
||||
statusCode,
|
||||
duration,
|
||||
};
|
||||
|
||||
console.group(`❌ API Error: ${method} ${url}`);
|
||||
console.log('⏰ Time:', entry.timestamp);
|
||||
if (statusCode) {
|
||||
console.log('📊 Status:', statusCode);
|
||||
}
|
||||
if (duration) {
|
||||
console.log('⏱️ Duration:', `${duration}ms`);
|
||||
}
|
||||
console.error('💥 Error:', error);
|
||||
console.groupEnd();
|
||||
|
||||
this.addLog(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 경고 로그
|
||||
*/
|
||||
logWarning(message: string, data?: any) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
const entry: ApiLogEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level: LogLevel.WARN,
|
||||
method: 'WARN',
|
||||
url: message,
|
||||
requestData: data,
|
||||
};
|
||||
|
||||
console.warn(`⚠️ API Warning: ${message}`, data);
|
||||
this.addLog(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 디버그 로그
|
||||
*/
|
||||
logDebug(message: string, data?: any) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
const entry: ApiLogEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
level: LogLevel.DEBUG,
|
||||
method: 'DEBUG',
|
||||
url: message,
|
||||
requestData: data,
|
||||
};
|
||||
|
||||
console.debug(`🔍 API Debug: ${message}`, data);
|
||||
this.addLog(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 추가 및 최대 개수 관리
|
||||
*/
|
||||
private addLog(entry: ApiLogEntry) {
|
||||
this.logs.push(entry);
|
||||
if (this.logs.length > this.maxLogs) {
|
||||
this.logs.shift(); // 가장 오래된 로그 제거
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 로그 조회
|
||||
*/
|
||||
getLogs(): ApiLogEntry[] {
|
||||
return [...this.logs];
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 레벨의 로그만 조회
|
||||
*/
|
||||
getLogsByLevel(level: LogLevel): ApiLogEntry[] {
|
||||
return this.logs.filter((log) => log.level === level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 에러 로그만 조회
|
||||
*/
|
||||
getErrors(): ApiLogEntry[] {
|
||||
return this.getLogsByLevel(LogLevel.ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 로그 삭제
|
||||
*/
|
||||
clearLogs() {
|
||||
this.logs = [];
|
||||
console.log('🗑️ API logs cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 통계 조회
|
||||
*/
|
||||
getStats() {
|
||||
const stats = {
|
||||
total: this.logs.length,
|
||||
byLevel: {
|
||||
[LogLevel.DEBUG]: 0,
|
||||
[LogLevel.INFO]: 0,
|
||||
[LogLevel.WARN]: 0,
|
||||
[LogLevel.ERROR]: 0,
|
||||
},
|
||||
averageDuration: 0,
|
||||
errorRate: 0,
|
||||
};
|
||||
|
||||
let totalDuration = 0;
|
||||
let countWithDuration = 0;
|
||||
|
||||
this.logs.forEach((log) => {
|
||||
stats.byLevel[log.level]++;
|
||||
if (log.duration) {
|
||||
totalDuration += log.duration;
|
||||
countWithDuration++;
|
||||
}
|
||||
});
|
||||
|
||||
if (countWithDuration > 0) {
|
||||
stats.averageDuration = totalDuration / countWithDuration;
|
||||
}
|
||||
|
||||
if (stats.total > 0) {
|
||||
stats.errorRate =
|
||||
(stats.byLevel[LogLevel.ERROR] / stats.total) * 100;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그 통계 출력
|
||||
*/
|
||||
printStats() {
|
||||
const stats = this.getStats();
|
||||
console.group('📊 API Logger Statistics');
|
||||
console.log('Total Logs:', stats.total);
|
||||
console.log('By Level:', stats.byLevel);
|
||||
console.log(
|
||||
'Average Duration:',
|
||||
`${stats.averageDuration.toFixed(2)}ms`
|
||||
);
|
||||
console.log('Error Rate:', `${stats.errorRate.toFixed(2)}%`);
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그를 JSON으로 내보내기
|
||||
*/
|
||||
exportLogs(): string {
|
||||
return JSON.stringify(this.logs, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그를 콘솔에 테이블로 출력
|
||||
*/
|
||||
printLogsAsTable() {
|
||||
if (this.logs.length === 0) {
|
||||
console.log('📭 No logs available');
|
||||
return;
|
||||
}
|
||||
|
||||
const tableData = this.logs.map((log) => ({
|
||||
Timestamp: log.timestamp,
|
||||
Level: log.level,
|
||||
Method: log.method,
|
||||
URL: log.url,
|
||||
Status: log.statusCode || '-',
|
||||
Duration: log.duration ? `${log.duration}ms` : '-',
|
||||
Error: log.error?.message || '-',
|
||||
}));
|
||||
|
||||
console.table(tableData);
|
||||
}
|
||||
}
|
||||
|
||||
// 싱글톤 인스턴스 생성
|
||||
export const apiLogger = new ApiLogger();
|
||||
|
||||
/**
|
||||
* API 호출 래퍼 함수
|
||||
* 자동으로 요청/응답을 로깅합니다
|
||||
*/
|
||||
export async function loggedFetch<T>(
|
||||
method: string,
|
||||
url: string,
|
||||
options?: RequestInit
|
||||
): Promise<T> {
|
||||
const startTime = apiLogger.logRequest(method, url, options?.body);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
method,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
apiLogger.logResponse(method, url, response.status, data, startTime);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || 'API request failed');
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
apiLogger.logError(method, url, error as Error, undefined, startTime);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 개발 도구를 window에 노출 (브라우저 콘솔에서 사용 가능)
|
||||
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
|
||||
(window as any).apiLogger = apiLogger;
|
||||
console.log(
|
||||
'💡 API Logger is available in console as "apiLogger"\n' +
|
||||
' - apiLogger.getLogs() - View all logs\n' +
|
||||
' - apiLogger.getErrors() - View errors only\n' +
|
||||
' - apiLogger.printStats() - View statistics\n' +
|
||||
' - apiLogger.printLogsAsTable() - View logs as table\n' +
|
||||
' - apiLogger.clearLogs() - Clear all logs'
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user