chore: auto release commit (2026-09-15 00:32:44)
All checks were successful
技能自动化发布 / release (push) Successful in 7s

This commit is contained in:
2026-09-15 00:32:44 +08:00
parent 693a5e4a53
commit 293b7d1591
15 changed files with 1715 additions and 16 deletions

View File

@@ -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")

View File

@@ -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

View File

@@ -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)