Files
sam-react-prod/src/lib/api/logger.ts
byeongcheolryu df3db155dd [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>
2025-11-23 16:10:27 +09:00

361 lines
7.7 KiB
TypeScript

// 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'
);
}