"""技能级文件日志。""" import logging import os import sys from logging.handlers import TimedRotatingFileHandler from util.constants import LOG_LOGGER_NAME, SKILL_SLUG from util.runtime_paths import get_skill_logs_dir def _skill_log_file_path() -> str: """主日志文件路径;可由 JIANGCHANG_ACCOUNT_MANAGER_LOG_FILE 覆盖为绝对路径。""" override = (os.getenv("JIANGCHANG_ACCOUNT_MANAGER_LOG_FILE") or "").strip() if override: parent = os.path.dirname(os.path.abspath(override)) if parent: os.makedirs(parent, exist_ok=True) return os.path.abspath(override) return os.path.join(get_skill_logs_dir(), f"{SKILL_SLUG}.log") def _log_level_from_env() -> int: v = (os.getenv("JIANGCHANG_LOG_LEVEL") or "INFO").strip().upper() return getattr(logging, v, None) or logging.INFO def get_skill_log_file_path() -> str: """主日志文件绝对路径(与 get_skill_logger 写入文件一致)。""" return _skill_log_file_path() def get_skill_logger() -> logging.Logger: """ 技能级日志:单文件按日切分(TimedRotatingFileHandler midnight),UTF-8。 环境变量:JIANGCHANG_LOG_LEVEL(默认 INFO)、JIANGCHANG_LOG_TO_STDERR=1 时 WARNING+ 同步 stderr、 JIANGCHANG_ACCOUNT_MANAGER_LOG_FILE 覆盖日志文件路径。 """ log = logging.getLogger(LOG_LOGGER_NAME) if log.handlers: return log log.setLevel(_log_level_from_env()) path = _skill_log_file_path() fh = TimedRotatingFileHandler( path, when="midnight", interval=1, backupCount=30, encoding="utf-8", delay=True, ) fh.setFormatter( logging.Formatter( "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s", datefmt="%Y-%m-%dT%H:%M:%S", ) ) log.addHandler(fh) if (os.getenv("JIANGCHANG_LOG_TO_STDERR") or "").strip().lower() in ( "1", "true", "yes", "on", ): sh = logging.StreamHandler(sys.stderr) sh.setLevel(logging.WARNING) sh.setFormatter(fh.formatter) log.addHandler(sh) log.propagate = False return log