"""运行状态 + 统一日志源。 日志只有一个出口 log(): * 控制台:带上时间/级别/key,docker compose logs 也能看 * 内存:RUNS[key]["log"],/api/log 供前端轮询 * SQLite:store.run_log_add,/api/log/history 重启后还能查 新代码不要再自己 print(),也不要直接写 run_log 表。 """ import threading import time from study_tool import store RUNS = {} RUNS_LOCK = threading.Lock() MAX_LINES = 1000 # 每个 key 内存里最多留 1000 行 TRIM_LINES = 300 # 超了先丢最旧的 300 行 def log(key, msg, level="info", persist=True): """写一条日志,返回拼好的整行。""" key = key or "system" line = "%s [%s] %s" % (time.strftime("%Y-%m-%d %H:%M:%S"), level, msg) try: print("%s [%s]" % (line, key), flush=True) except UnicodeEncodeError: # Windows 控制台 GBK 编不了 ⚠ 之类 print(("%s [%s]" % (line, key)).encode("gbk", "replace").decode("gbk"), flush=True) with RUNS_LOCK: st = RUNS.setdefault(key, {}) logs = st.setdefault("log", []) logs.append(line) if len(logs) > MAX_LINES: del logs[:TRIM_LINES] st["updated"] = time.time() if persist: try: store.run_log_add(key, line) except Exception: pass return line def set_state(key, **kw): with RUNS_LOCK: st = RUNS.setdefault(key, {}) st.update(kw) st["updated"] = time.time() store.run_state_set(key, RUNS.get(key) or {})