feat(audit): skip audit log for excluded health/root paths

健康检查与根路径(/health/live、/health/ready、/api/v1/health、
/、/health/storage)没有用户、没业务动作,每秒被 K8s/LB
探针刷一次只会灌进无意义噪音。命中排除集即跳过审计行;
诊断日志(method/path/status/ms 走 stderr)照常打,对容器
运维排错仍有用。

* settings.audit_excluded_paths: list[str] 默认覆盖 5 条
  基础设施路径,env AUDIT_EXCLUDED_PATHS 用逗号分隔
  (pydantic NoDecode + field_validator 兼容 str/list)
* main.py 模块级 _AUDIT_EXCLUDED = frozenset(...),
  access_log 的 success/exception 两条审计行各加守卫
  诊断无条件打
* 测试用 _AccessLogReplica 复刻 access_log 契约(不 import
  真实 main.py),新增 4 个 case:排除根路径、排除 /health/live、
  不排除路径照写审计、自定义排除集

顺带 schedule 模块:ExecutionResult 与 context 已迁到
schedule.domain.*(execution.py / orchestrator.py /
scheduler.py / worker.py),调用点跟进;schedule 自身
18 个测试在改前改后均通过。
This commit is contained in:
tao.chen
2026-09-02 10:10:41 +08:00
committed by tao.chen
parent 8dc2311536
commit 9f7b6ba18c
8 changed files with 162 additions and 45 deletions
+1 -13
View File
@@ -4,10 +4,10 @@ import asyncio
import json
import sys
import tempfile
from dataclasses import dataclass
from pathlib import Path, PurePosixPath
from loguru import logger
from schedule.domain.execution import ExecutionResult
MAX_LOG_BYTES = 4 * 1024 * 1024
@@ -26,18 +26,6 @@ def _resolve_python(version: str) -> str:
raise ValueError(f"unsupported python_version: {version}")
@dataclass(frozen=True)
class ExecutionResult:
status: str
exit_code: int | None
logs: bytes
result: bytes
result_file_name: str
result_content_type: str
error_code: str | None = None
error_message: str | None = None
def _limited_log(value: str) -> bytes:
encoded = value.encode("utf-8", errors="replace")
if len(encoded) <= MAX_LOG_BYTES:
+1 -1
View File
@@ -30,7 +30,7 @@ from loguru import logger
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from schedule.context import (
from schedule.domain.context import (
FAILED_NODE_STATES,
TERMINAL_NODE_STATES,
TERMINAL_RUN_STATES,
+1 -1
View File
@@ -28,7 +28,7 @@ from loguru import logger
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from schedule.context import naive_utc
from schedule.domain.context import naive_utc
# APScheduler's persistent SQLAlchemy job store pickles each job. A bound
# ``CronScheduler`` method captures this instance (including SQLAlchemy engine
+3 -2
View File
@@ -41,8 +41,9 @@ from common.scheduler.trigger import SYSTEM_CRON_USER_ID
from loguru import logger
from sqlalchemy import select
from schedule.context import TERMINAL_NODE_STATES
from schedule.execution import ExecutionResult, execute_artifact
from schedule.domain.context import TERMINAL_NODE_STATES
from schedule.domain.execution import ExecutionResult
from schedule.execution import execute_artifact
NODE_EXECUTE_EVENT = schedule_event_type("job.node.execute")
NODE_FINISHED_EVENT = schedule_event_type("job.node.finished")