feat(日志): 任务步骤明细表 + 「日志 → 步骤明细」面板(按设备/任务/时间过滤、按运行归组、导出 CSV)
文本日志只能 grep,"这台设备这次运行为什么失败"翻起来很费劲。新增一张 **结构化**的步骤明细表,把"哪一步、什么类型、哪个选择器、结果、耗时"落库。 - core/models.py:新增 task_step_log(run_id/job/设备/step_path/selector/ result/detail/duration_ms),索引 run_id、created_at、(serial,created_at)、 (job_id,created_at); - core/step_log.py(新):**专用写线程 + 有界队列**批量落库——步骤执行是热路径, 任务线程只 put_nowait(实测 9000 次入队 31ms),队列满丢弃并计数,绝不阻塞; 另有保留期清理(默认 14 天,每日 04:13 + 每次启动); - tasks/generic/task.py:_exec_one 记一条(异常=error / handler 返回 False=miss / 未知类型=unknown / 概率未触发=skip);_exec_steps 维护路径栈得到 "2.1.3" 这样的嵌套位置;**单次运行封顶 2000 条**——forever 循环任务否则会写爆表; - 运行上下文 ctx(run_id/job_id/job_name/device_name)由 TaskManager 生成, 经 create_worker(serial, params, ctx=None) 传入 worker(扩展点向后兼容); - 接口:/api/step_logs(过滤+分页)、/runs(按运行归组)、/filters(下拉选项)、 /download(CSV,带 BOM); - 前端:日志页拆成「文件日志 / 步骤明细」子分栏 + static/admin/steplog.js。 红线:新表自动进备份覆盖清单(SUMMARY_TABLES 由元数据派生),已补 TABLE_LABELS 中文标签,导出实测 14 张表、coverage_missing 为空。 文档:DATA_MODEL §2.8/§1、API §10.2、ARCHITECTURE §1.1/§2.2/§3.1、 DEVELOPMENT §5.2 与红线表、DEPLOY §5.2、TASK_DEV §8.3、README。
This commit is contained in:
@@ -369,6 +369,40 @@ class DeviceInstallLog(db.Model):
|
||||
created_at = db.Column(db.String(20), default="")
|
||||
|
||||
|
||||
class TaskStepLog(db.Model):
|
||||
"""任务步骤明细:每一次步骤执行的落库记录(「日志 → 步骤明细」页)。
|
||||
|
||||
与 `logs/task.log` 的分工:文本日志是**排障时的原始现场**(什么都往里写、
|
||||
10MB 滚动),本表是**结构化的一份**——设备/任务/步骤/结果/耗时都是列,
|
||||
所以能按设备、任务、时间、结果过滤和统计,文本日志只能 grep。
|
||||
|
||||
写入方是 `core/step_log.py` 的专用写线程(异步批量落库),**任务线程不直接
|
||||
写库**:一次运行可能上万步,每步一次 INSERT 会拖慢热路径。
|
||||
|
||||
保留期由 `core/step_log.py` 的清理任务控制(默认 14 天,见该模块常量)。
|
||||
"""
|
||||
__tablename__ = "task_step_log"
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
run_id = db.Column(db.String(24), default="", index=True) # 一次运行=设备×任务×第几次尝试
|
||||
job_id = db.Column(db.String(32), default="")
|
||||
job_name = db.Column(db.String(120), default="")
|
||||
serial = db.Column(db.String(120), default="")
|
||||
device_name = db.Column(db.String(80), default="")
|
||||
step_path = db.Column(db.String(32), default="") # 嵌套位置,如 "2.1.3"
|
||||
step_label = db.Column(db.String(120), default="")
|
||||
step_type = db.Column(db.String(40), default="") # click_el / loop / input_text …
|
||||
selector = db.Column(db.String(300), default="") # 元素选择器(长选择器截断)
|
||||
result = db.Column(db.String(16), default="") # ok/skip/miss/error/unknown/cap
|
||||
detail = db.Column(db.String(500), default="") # 异常消息、跳过原因等
|
||||
duration_ms = db.Column(db.Integer, default=0)
|
||||
created_at = db.Column(db.String(20), default="", index=True)
|
||||
|
||||
__table_args__ = (
|
||||
db.Index("ix_step_log_serial_ts", "serial", "created_at"),
|
||||
db.Index("ix_step_log_job_ts", "job_id", "created_at"),
|
||||
)
|
||||
|
||||
|
||||
class AgentConversation(db.Model):
|
||||
"""AI 控制台会话:整个消息序列以 JSON 存在一行里(单会话几十 KB,够用)。"""
|
||||
__tablename__ = "agent_conversation"
|
||||
|
||||
Reference in New Issue
Block a user