108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
"""logging 配置:降级到 stderr,避免污染 stdout(stdout 留给结构化输出如 JSON)。
|
||
|
||
优先用 jiangchang_skill_core 提供的 logging;本地无该包时降级到 stderr。
|
||
|
||
v1.1.18:本地降级路径追加 RotatingFileHandler(按 LOG_FILE env 决定是否落文件)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import logging.handlers
|
||
import os
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
from util.constants import (
|
||
LOG_LOGGER_NAME,
|
||
SKILL_SLUG,
|
||
SKILL_VERSION,
|
||
)
|
||
|
||
_configured = False
|
||
|
||
|
||
def setup_skill_logging() -> logging.Logger:
|
||
"""初始化 skill logger(幂等)。
|
||
|
||
本地降级行为(无 jiangchang_skill_core 时):
|
||
- 若 LOG_FILE 已设:追加 RotatingFileHandler(LOG_MAX_BYTES × LOG_BACKUP_COUNT)
|
||
- 始终追加 StreamHandler(sys.stderr)
|
||
- logger level 取自 LOG_LEVEL(默认 INFO)
|
||
"""
|
||
global _configured
|
||
logger = logging.getLogger(LOG_LOGGER_NAME)
|
||
if _configured:
|
||
return logger
|
||
|
||
# 优先用宿主提供的 skill logger 配置
|
||
try:
|
||
from jiangchang_skill_core.logging_config import setup_skill_logging as _host_setup
|
||
|
||
_host_setup(SKILL_SLUG, LOG_LOGGER_NAME)
|
||
_configured = True
|
||
return logging.getLogger(LOG_LOGGER_NAME)
|
||
except ImportError:
|
||
pass
|
||
|
||
# logger 级别
|
||
level_name = os.environ.get("LOG_LEVEL", "INFO").upper()
|
||
logger.setLevel(getattr(logging, level_name, logging.INFO))
|
||
|
||
formatter = logging.Formatter(
|
||
fmt=f"%(asctime)s [{SKILL_SLUG}] %(levelname)s %(message)s",
|
||
datefmt="%Y-%m-%d %H:%M:%S",
|
||
)
|
||
|
||
# 本地降级:可选 RotatingFileHandler
|
||
log_file_str = os.environ.get("LOG_FILE")
|
||
if log_file_str:
|
||
log_path = Path(log_file_str)
|
||
if not log_path.is_absolute():
|
||
from util.runtime_paths import get_logs_dir
|
||
|
||
log_path = get_logs_dir() / log_file_str
|
||
log_path.parent.mkdir(parents=True, exist_ok=True)
|
||
max_bytes = int(os.environ.get("LOG_MAX_BYTES", str(5 * 1024 * 1024)))
|
||
backup_count = int(os.environ.get("LOG_BACKUP_COUNT", "5"))
|
||
file_handler = logging.handlers.RotatingFileHandler(
|
||
log_path,
|
||
maxBytes=max_bytes,
|
||
backupCount=backup_count,
|
||
encoding="utf-8",
|
||
)
|
||
file_handler.setFormatter(formatter)
|
||
logger.addHandler(file_handler)
|
||
|
||
# 始终保留 stderr handler
|
||
handler = logging.StreamHandler(sys.stderr)
|
||
handler.setFormatter(formatter)
|
||
logger.addHandler(handler)
|
||
logger.propagate = False
|
||
_configured = True
|
||
logger.info("logging initialized (local fallback) v%s", SKILL_VERSION)
|
||
return logger
|
||
|
||
|
||
def get_skill_logger() -> logging.Logger:
|
||
"""获取 skill logger(不会重复 setup)。"""
|
||
if not _configured:
|
||
return setup_skill_logging()
|
||
return logging.getLogger(LOG_LOGGER_NAME)
|
||
|
||
|
||
def reset_for_tests() -> None:
|
||
"""测试用:重置 _configured flag,让 setup_skill_logging 重新初始化。
|
||
|
||
必须在每个修改日志配置的测试 fixture 里调用,否则 env 改动不会生效。
|
||
"""
|
||
global _configured
|
||
logger = logging.getLogger(LOG_LOGGER_NAME)
|
||
for h in list(logger.handlers):
|
||
try:
|
||
h.close()
|
||
except Exception:
|
||
pass
|
||
logger.removeHandler(h)
|
||
_configured = False
|