108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
import gzip
|
||
import logging
|
||
import logging.handlers
|
||
import os
|
||
import shutil
|
||
from pathlib import Path
|
||
|
||
LOG_DIR = os.getenv("LOG_DIR", "logs")
|
||
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
|
||
Path(LOG_DIR).mkdir(parents=True, exist_ok=True)
|
||
LOG_FILE = os.path.join(LOG_DIR, "AutoCoinTrader.log")
|
||
|
||
logger = logging.getLogger("macd_alarm")
|
||
_logger_configured = False
|
||
|
||
# 거래소 및 계산 상수
|
||
# 부동소수점 비교용 엡실론 (일반적 정밀도)
|
||
FLOAT_EPSILON = 1e-10
|
||
|
||
# 거래소별 최소 수량 (Upbit 기준)
|
||
MIN_TRADE_AMOUNT = 1e-8 # 0.00000001 (암호화폐 최소 단위)
|
||
|
||
# 최소 주문 금액 (KRW)
|
||
MIN_KRW_ORDER = 5000 # Upbit 최소 주문 금액
|
||
|
||
# 데이터 파일 경로 상수 (중앙 집중 관리)
|
||
DATA_DIR = Path("data")
|
||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||
HOLDINGS_FILE = str(DATA_DIR / "holdings.json")
|
||
TRADES_FILE = str(DATA_DIR / "trades.json")
|
||
PENDING_ORDERS_FILE = str(DATA_DIR / "pending_orders.json")
|
||
|
||
|
||
class CompressedRotatingFileHandler(logging.handlers.RotatingFileHandler):
|
||
"""RotatingFileHandler with gzip compression for rotated logs."""
|
||
|
||
def rotation_filename(self, default_name):
|
||
"""Append .gz to rotated log files."""
|
||
return default_name + ".gz"
|
||
|
||
def rotate(self, source, dest):
|
||
"""Compress the rotated log file."""
|
||
if os.path.exists(source):
|
||
with open(source, "rb") as f_in:
|
||
with gzip.open(dest, "wb") as f_out:
|
||
shutil.copyfileobj(f_in, f_out)
|
||
os.remove(source)
|
||
|
||
|
||
def setup_logger(dry_run: bool):
|
||
"""
|
||
Configure logging with rotation and compression.
|
||
|
||
Args:
|
||
dry_run: If True, also output to console. If False, only to file.
|
||
|
||
Log Rotation Strategy:
|
||
- Size-based: 10MB per file, keep 7 backups (total ~80MB)
|
||
- Compression: Old logs are gzipped (saves ~70% space)
|
||
|
||
Log Levels (production recommendation):
|
||
- dry_run=True: INFO (development/testing)
|
||
- dry_run=False: INFO (production - retain important trading logs)
|
||
|
||
⚠️ CRITICAL: Production mode uses INFO level to ensure trading events are logged.
|
||
This is essential for auditing buy/sell orders and debugging issues.
|
||
For high-volume environments, adjust LOG_LEVEL via environment variable.
|
||
"""
|
||
global logger, _logger_configured
|
||
if _logger_configured:
|
||
return
|
||
|
||
logger.handlers.clear()
|
||
|
||
# Use INFO level for both dry_run and production to ensure trading events are logged
|
||
# Production systems can override via LOG_LEVEL environment variable if needed
|
||
effective_level = getattr(logging, LOG_LEVEL, logging.INFO)
|
||
logger.setLevel(effective_level)
|
||
|
||
formatter = logging.Formatter("%(asctime)s - %(levelname)s - [%(threadName)s] - %(message)s")
|
||
|
||
# Console handler (only in dry_run mode)
|
||
if dry_run:
|
||
ch = logging.StreamHandler()
|
||
ch.setLevel(effective_level)
|
||
ch.setFormatter(formatter)
|
||
logger.addHandler(ch)
|
||
|
||
# Size-based rotating file handler with compression (only one rotation strategy)
|
||
fh_size = CompressedRotatingFileHandler(
|
||
LOG_FILE,
|
||
maxBytes=10 * 1024 * 1024,
|
||
backupCount=7,
|
||
encoding="utf-8", # 10MB per file # Keep 7 backups
|
||
)
|
||
fh_size.setLevel(effective_level)
|
||
fh_size.setFormatter(formatter)
|
||
logger.addHandler(fh_size)
|
||
|
||
_logger_configured = True
|
||
|
||
logger.info(
|
||
"[SYSTEM] 로그 설정 완료: level=%s, size_rotation=%dMB×%d (일별 로테이션 제거됨)",
|
||
logging.getLevelName(effective_level),
|
||
10,
|
||
7,
|
||
)
|