UNPKG

@homecheck/logger

Version:

A simple logger for Web, Node, Capacitor apps.

254 lines (253 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getFormattedTimestamp = getFormattedTimestamp; exports.getStackTrace = getStackTrace; exports.getOsInfo = getOsInfo; exports.formatLogString = formatLogString; exports.formatDataToString = formatDataToString; function getFormattedTimestamp(utcOffset) { try { const now = new Date(); // UTC 오프셋 적용 const offsetMillis = utcOffset * 60 * 60 * 1000; const localTime = new Date(now.getTime() + offsetMillis); const isoString = localTime.toISOString(); const [datePart, timePart] = isoString.split('T'); const timeWithoutMillis = timePart.split('.')[0]; return `${datePart} ${timeWithoutMillis}`; } catch (error) { // 에러 발생 시 기본 ISO 문자열 반환 return new Date().toISOString(); } } function getStackTrace(project, skipFrames = 3) { try { const stack = new Error().stack || ''; const stackLines = stack.split('\n'); // 브라우저 환경인지 확인 const isBrowser = typeof window !== 'undefined'; // 스택 트레이스에서 Logger 내부 함수를 건너뛰기 let callerLine = ''; // Logger 클래스의 메서드를 호출한 실제 위치 찾기 for (let i = skipFrames; i < stackLines.length; i++) { const line = stackLines[i] || ''; // Logger 내부 함수면 건너뜀 if (line.includes('@homecheck/logger') && (line.includes('/index.js') || line.includes('/utils.js'))) continue; // 브라우저 환경에서 Logger 내부 함수 건너뛰기 if (isBrowser && (line.includes('Logger.') || line.includes('logger.') || line.includes('/logger/'))) continue; // 외부 호출자 찾음 callerLine = line; break; } // 호출자를 찾지 못한 경우 기본값 if (!callerLine && stackLines.length > skipFrames) callerLine = stackLines[skipFrames]; // 브라우저 환경에서의 스택 트레이스 파싱 if (isBrowser) { // 공백이 없는 형태의 스택 트레이스 처리 if (callerLine.includes('@')) { const parts = callerLine.split('@'); const functionName = parts[0]; // URL과 라인 정보 추출 const urlParts = parts[1].split(':'); const lineNumber = urlParts[urlParts.length - 2] || ''; const columnNumber = urlParts[urlParts.length - 1] || ''; // URL 부분 재구성 (마지막 두 부분(라인, 컬럼)을 제외) const urlBase = urlParts.slice(0, urlParts.length - 2).join(':'); // 파일명 추출 const filePath = urlBase || 'unknown'; const fileName = filePath.split('/').pop() || filePath; return { function: functionName, path: filePath, file: fileName, project, line: lineNumber && columnNumber ? `:${lineNumber}:${columnNumber}` : '' }; } let match = callerLine.match(/at\s+([^@]+)@([^:]+):(\d+):(\d+)/); if (match) { return { function: match[1].trim(), path: match[2], file: match[2].split('/').pop() || match[2], project, line: `:${match[3]}:${match[4]}` }; } match = callerLine.match(/([^@]+)@([^:]+:[^:]+):(\d+):(\d+)/); if (match) { return { function: match[1].trim(), path: match[2], file: match[2].split('/').pop() || match[2], project, line: `:${match[3]}:${match[4]}` }; } match = callerLine.match(/at\s+(.+?)(?:\s+\((.+?):(\d+):(\d+)\))?$/); if (match) { const functionName = match[1] && !match[1].includes(':') ? match[1].trim() : 'anonymous'; const filePath = match[2] || (match[1] && match[1].includes(':') ? match[1].split(':')[0] : 'unknown'); const lineNumber = match[3] || (match[1] && match[1].includes(':') ? match[1].split(':')[1] : ''); const columnNumber = match[4] || (match[1] && match[1].includes(':') ? match[1].split(':')[2] : ''); return { function: functionName, path: filePath, file: filePath.split('/').pop() || filePath, project, line: lineNumber && columnNumber ? `:${lineNumber}:${columnNumber}` : '' }; } } // Node.js 환경 또는 기본 파싱 로직 // 파일 정보 const fileMatch = callerLine.match(/\((.+)\)/) || callerLine.match(/at\s+(.+)$/); let path = fileMatch ? fileMatch[1].split(':')[0] : 'unknown'; // 파일명 const file = path.split('/').pop() || path; // 라인 정보 let line = ''; const lineMatch = callerLine.match(/:(\d+):(\d+)/); if (lineMatch) line = `:${lineMatch[1]}:${lineMatch[2]}`; // 함수명 const functionMatch = callerLine.match(/at\s+([^(]+)\s*\(/) || callerLine.match(/at\s+(.+)$/); let functionName = functionMatch ? functionMatch[1].trim() : 'unknown'; return { file, function: functionName, project, path, line }; } catch (error) { // 오류 발생 시 기본값 return { file: 'unknown', function: 'unknown', project, path: 'unknown', line: '' }; } } function getOsInfo() { try { if (typeof window !== 'undefined') { // 브라우저 환경 const userAgent = navigator.userAgent; // userAgent에서 플랫폼 정보 추출 let platform = 'unknown'; if (userAgent.includes('Windows')) platform = 'Windows'; else if (userAgent.includes('Macintosh')) platform = 'Mac'; else if (userAgent.includes('Linux')) platform = 'Linux'; else if (userAgent.includes('Android')) platform = 'Android'; else if (userAgent.includes('iOS') || /iPhone|iPad|iPod/.test(userAgent)) platform = 'iOS'; return `${platform} ${userAgent}`; } else { // Node.js 환경 // @ts-ignore const os = require('os'); return `${os.platform()} ${os.release()}`; } } catch (error) { return 'unknown'; } } function formatLogString(logData, withColors = false) { const level = logData.level || 'info'; const timestamp = logData.timestamp || ''; const text = logData.text || ''; const functionName = logData.function || ''; const path = logData.path || ''; const line = logData.line || ''; const project = logData.project || ''; const log = logData.log || ''; const [dir, file] = path.match(/(.*\/|)([^/]*)$/).slice(1); // 색상 코드 (withColors가 true일 때만 적용) const levelColor = withColors ? getLevelColor(level) : ''; const resetColor = withColors ? '\x1b[0m' : ''; const dimColor = withColors ? '\x1b[2m' : ''; // 마크다운 문자 (withColors false) const codeText = !withColors ? '`' : ''; const codeBlock = !withColors ? '```' : ''; let logString = ` ${codeText}${levelColor}[${level.toUpperCase()}]${resetColor}${codeText} ${codeText}${project}${codeText} ${codeText}${dimColor}${timestamp}${resetColor}${codeText} ${codeBlock} ${dimColor}at${resetColor} ${functionName} ${dimColor}${dir}${resetColor}${file}${line} `; let content = ''; // 텍스트 데이터 추가 if (text && text.trim()) content += text; if (log && log.trim()) { if (content) content += '\n\n'; content += log; } if (content) logString += ` ${content} `; logString += ` ${codeBlock} `; return logString; } // 로그 레벨에 따른 색상 코드 반환 function getLevelColor(level) { switch (level) { case 'debug': return '\x1b[36m'; // 청록색 case 'info': return '\x1b[32m'; // 녹색 case 'warn': return '\x1b[33m'; // 노란색 case 'error': return '\x1b[31m'; // 빨간색 case 'fatal': return '\x1b[35m'; // 자주색 default: return '\x1b[32m'; // 기본 녹색 } } // 데이터를 문자열로 변환하는 함수 function formatDataToString(data) { if (data === undefined) return 'undefined'; if (data === null) return 'null'; if (typeof data === 'string') return data; if (typeof data === 'number' || typeof data === 'boolean' || typeof data === 'bigint') return String(data); if (data instanceof Error) { // 에러 객체를 문자열로 변환 let result = `${data.name || 'Error'}: ${data.message || ''}`; // 추가 속성이 있는지 확인 const additionalProps = {}; for (const key in data) { if (key !== 'name' && key !== 'message' && key !== 'stack') additionalProps[key] = data[key]; } // 추가 속성이 있으면 JSON으로 표시 if (Object.keys(additionalProps).length > 0) result += '\n' + JSON.stringify(additionalProps, null, 2); return result; } if (typeof data === 'object') { try { return JSON.stringify(data, null, 2); } catch (e) { return '[Object]'; } } return String(data); }