chore: auto release commit (2026-09-15 00:32:44)
All checks were successful
技能自动化发布 / release (push) Successful in 7s
All checks were successful
技能自动化发布 / release (push) Successful in 7s
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
"""
|
||||
|
||||
SKILL_SLUG = "alert-contract-expiring-hr"
|
||||
SKILL_VERSION = "1.1.17"
|
||||
SKILL_VERSION = "1.1.18"
|
||||
LOG_LOGGER_NAME = "openclaw.skill.alert_contract_expiring_hr"
|
||||
PLATFORM_KIT_MIN_VERSION = "1.2.11"
|
||||
|
||||
@@ -87,3 +87,13 @@ CARD_APPEAR_WAIT_MS = 3_000 # 「续聘材料包」click 后等卡片出
|
||||
|
||||
# dump
|
||||
DUMP_PATH_PARENT_MKDIR = True # _dump_page 是否自动创建父目录
|
||||
|
||||
# ----- v1.1.18:日志落文件 -----
|
||||
LOG_FILE_DEFAULT_NAME = "alert-contract-expiring-hr.log"
|
||||
LOG_MAX_BYTES_DEFAULT = 5 * 1024 * 1024 # 5MB
|
||||
LOG_BACKUP_COUNT_DEFAULT = 5
|
||||
|
||||
# ----- v1.1.18:材料下载队列 -----
|
||||
DOWNLOAD_QUEUE_MAX_ATTEMPTS_DEFAULT = 3
|
||||
DOWNLOAD_QUEUE_KINDS = ("platform_button", "local_upload")
|
||||
DOWNLOAD_QUEUE_STATUSES = ("pending", "running", "success", "failed")
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
"""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
|
||||
from util.constants import (
|
||||
LOG_LOGGER_NAME,
|
||||
SKILL_SLUG,
|
||||
SKILL_VERSION,
|
||||
)
|
||||
|
||||
_configured = False
|
||||
|
||||
|
||||
def setup_skill_logging() -> logging.Logger:
|
||||
"""初始化 skill 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:
|
||||
@@ -30,15 +45,38 @@ def setup_skill_logging() -> logging.Logger:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# 本地降级:基础 stderr handler,UTF-8 safe
|
||||
handler = logging.StreamHandler(sys.stderr)
|
||||
handler.setFormatter(
|
||||
logging.Formatter(
|
||||
fmt=f"%(asctime)s [{SKILL_SLUG}] %(levelname)s %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
)
|
||||
# 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",
|
||||
)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
# 本地降级:可选 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
|
||||
@@ -51,3 +89,19 @@ def get_skill_logger() -> logging.Logger:
|
||||
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
|
||||
|
||||
@@ -66,6 +66,30 @@ def get_dispatch_dir() -> Path:
|
||||
return get_data_dir() / "dispatch"
|
||||
|
||||
|
||||
def get_logs_dir() -> Path:
|
||||
"""返回日志目录(RotatingFileHandler 默认落地点)。"""
|
||||
env_dir = os.environ.get("JIANGCHANG_LOGS_DIR")
|
||||
if env_dir:
|
||||
return Path(env_dir)
|
||||
return get_data_dir() / "logs"
|
||||
|
||||
|
||||
def get_input_dir() -> Path:
|
||||
"""返回用户主动投放的输入目录(含 contracts/*.csv + materials/*.{pdf,docx,doc})。"""
|
||||
env_dir = os.environ.get("INPUT_DIR")
|
||||
if env_dir:
|
||||
return Path(env_dir)
|
||||
return get_data_dir() / "input"
|
||||
|
||||
|
||||
def get_input_contracts_dir() -> Path:
|
||||
return get_input_dir() / "contracts"
|
||||
|
||||
|
||||
def get_input_materials_dir() -> Path:
|
||||
return get_input_dir() / "materials"
|
||||
|
||||
|
||||
def get_examples_dir() -> Path:
|
||||
"""返回样例数据目录。"""
|
||||
return get_skill_root() / "examples"
|
||||
@@ -78,5 +102,13 @@ def get_templates_dir() -> Path:
|
||||
|
||||
def ensure_dirs() -> None:
|
||||
"""确保所有需要的目录存在。"""
|
||||
for d in (get_data_dir(), get_output_dir(), get_dispatch_dir()):
|
||||
for d in (
|
||||
get_data_dir(),
|
||||
get_output_dir(),
|
||||
get_dispatch_dir(),
|
||||
get_logs_dir(),
|
||||
get_input_dir(),
|
||||
get_input_contracts_dir(),
|
||||
get_input_materials_dir(),
|
||||
):
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
Reference in New Issue
Block a user