mirror of
https://git.hmsn.ink/coin/bot.git
synced 2026-03-19 15:55:01 +09:00
180 lines
5.3 KiB
JavaScript
180 lines
5.3 KiB
JavaScript
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
// 로그 디렉토리 생성
|
|
const logDir = './logs';
|
|
if (!fs.existsSync(logDir)) {
|
|
fs.mkdirSync(logDir, { recursive: true });
|
|
}
|
|
|
|
// 로그 레벨 정의
|
|
const logLevels = {
|
|
DEBUG: 0,
|
|
INFO: 1,
|
|
WARN: 2,
|
|
ERROR: 3,
|
|
CRITICAL: 4
|
|
};
|
|
|
|
const logLevelNames = Object.keys(logLevels);
|
|
const currentLogLevel = logLevels.info; // 설정에 따라 변경 가능
|
|
|
|
// 타임스탬프 생성
|
|
const getTimestamp = () => new Date().toISOString();
|
|
|
|
// 호출자 정보 추출 (ESM 호환)
|
|
const getCallerInfo = () => {
|
|
const err = new Error();
|
|
const stack = err.stack.split('\n');
|
|
|
|
// 스택에서 호출자 위치 찾기
|
|
let callerIndex = 2; // 기본값 (0: Error, 1: getCallerInfo, 2: logger function)
|
|
|
|
// 'at '로 시작하는 라인 찾기
|
|
for (let i = 2; i < stack.length; i++) {
|
|
if (stack[i].includes('at ')) {
|
|
callerIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (stack.length > callerIndex) {
|
|
const callerLine = stack[callerIndex].trim();
|
|
|
|
// 파일 경로 추출 (Windows와 Unix 경로 모두 처리)
|
|
const fileMatch = callerLine.match(/\((.*?):\d+:\d+\)/) ||
|
|
callerLine.match(/(.*?):\d+:\d+$/);
|
|
|
|
if (fileMatch && fileMatch[1]) {
|
|
const filePath = fileMatch[1];
|
|
const fileName = path.basename(filePath);
|
|
return fileName;
|
|
}
|
|
}
|
|
|
|
return 'unknown';
|
|
};
|
|
|
|
// 로그 포맷팅
|
|
const formatLog = (level, message, metadata = {}) => {
|
|
const timestamp = getTimestamp();
|
|
const callerInfo = getCallerInfo();
|
|
const levelName = logLevelNames[level];
|
|
|
|
let logMessage = `[${levelName}] ${timestamp} [${callerInfo}] ${message}`;
|
|
|
|
if (Object.keys(metadata).length > 0) {
|
|
try {
|
|
logMessage += `\n Meta: ${JSON.stringify(metadata, null, 2)}`;
|
|
} catch (e) {
|
|
logMessage += `\n Meta: [Circular or invalid object]`;
|
|
}
|
|
}
|
|
|
|
return logMessage;
|
|
};
|
|
|
|
// 로그 파일 쓰기
|
|
const writeLog = (logMessage, level) => {
|
|
if (level < currentLogLevel) return;
|
|
|
|
// 콘솔 출력
|
|
if (level >= logLevels.ERROR) {
|
|
console.error(logMessage);
|
|
} else {
|
|
console.log(logMessage);
|
|
}
|
|
|
|
// 파일 로깅
|
|
try {
|
|
const date = new Date().toISOString().split('T')[0];
|
|
const logFile = path.join(logDir, `trading-${date}.log`);
|
|
fs.appendFileSync(logFile, logMessage + '\n');
|
|
} catch (e) {
|
|
// 파일 로깅 실패 시 콘솔에 경고
|
|
console.error(`[LOGGER] Failed to write log file: ${e.message}`);
|
|
}
|
|
};
|
|
|
|
// 로거 인스턴스 생성
|
|
const createLogger = () => {
|
|
return {
|
|
debug: (message, metadata = {}) => {
|
|
const logMessage = formatLog(logLevels.DEBUG, message, metadata);
|
|
writeLog(logMessage, logLevels.DEBUG);
|
|
},
|
|
|
|
info: (message, metadata = {}) => {
|
|
const logMessage = formatLog(logLevels.INFO, message, metadata);
|
|
writeLog(logMessage, logLevels.INFO);
|
|
},
|
|
|
|
warn: (message, metadata = {}) => {
|
|
const logMessage = formatLog(logLevels.WARN, message, metadata);
|
|
writeLog(logMessage, logLevels.WARN);
|
|
},
|
|
|
|
error: (message, error = null, metadata = {}) => {
|
|
const errorMetadata = { ...metadata };
|
|
|
|
if (error) {
|
|
errorMetadata.error = {
|
|
message: error.message || 'Unknown error',
|
|
stack: error.stack ?
|
|
error.stack.split('\n').slice(0, 5).join('\n') :
|
|
'No stack trace'
|
|
};
|
|
}
|
|
|
|
const logMessage = formatLog(logLevels.ERROR, message, errorMetadata);
|
|
writeLog(logMessage, logLevels.ERROR);
|
|
},
|
|
|
|
critical: (message, error = null, metadata = {}) => {
|
|
const errorMetadata = { ...metadata };
|
|
|
|
if (error) {
|
|
errorMetadata.error = {
|
|
message: error.message || 'Unknown critical error',
|
|
stack: error.stack || 'No stack trace'
|
|
};
|
|
}
|
|
|
|
const logMessage = formatLog(logLevels.CRITICAL, message, errorMetadata);
|
|
writeLog(logMessage, logLevels.CRITICAL);
|
|
},
|
|
|
|
logPosition: (position, message) => {
|
|
if (!position) return;
|
|
|
|
const metadata = {
|
|
position: {
|
|
id: position.id,
|
|
side: position.side,
|
|
entry: position.entry,
|
|
qty: position.qty,
|
|
sl: position.sl,
|
|
tp: position.tp,
|
|
openTime: position.openTime ?
|
|
new Date(position.openTime).toISOString() : 'N/A'
|
|
}
|
|
};
|
|
|
|
if (position.closed) {
|
|
metadata.position.exitPrice = position.exitPrice;
|
|
metadata.position.exitReason = position.exitReason;
|
|
metadata.position.pnl = position.pnl;
|
|
metadata.position.closeTime = position.closeTime ?
|
|
new Date(position.closeTime).toISOString() : 'N/A';
|
|
}
|
|
|
|
this.info(message, metadata);
|
|
}
|
|
};
|
|
};
|
|
|
|
// 싱글톤 인스턴스 생성
|
|
const logger = createLogger();
|
|
|
|
// 모듈 내보내기
|
|
export default logger; |