Run events in the database: Runs page and Agent run card on the Dokploy Portal
Snapshot of D:\Ecw Process 65c92c5 (2026-10-30). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d500f750da
commit
840272032a
9 files changed
+655
-46
No files matched your search
@@ -8,6 +8,11 @@ writes `Logs\\events_<run>.jsonl` beside `run_<run>.log`: one JSON object a
|
||||
line, each with its time to the millisecond, flushed at once so a killed Run
|
||||
keeps what it wrote. The Portal's Runs page reads these files.
|
||||
|
||||
Every event is also copied to `dbo.CV_RunEvent` (2026-10-30, with
|
||||
`RESULTS_DB=1`): the Portal on Dokploy cannot read this PC's Logs folder.
|
||||
The file stays the record; the row is the same JSON line. The first
|
||||
database error switches the copy off for the rest of the Run.
|
||||
|
||||
An event never stops a Run: anything that goes wrong writing one drops that
|
||||
event and nothing else. Nothing secret goes in — no OTP code, no password,
|
||||
no eCW username: the callers pass fixed fields, never a whole log line.
|
||||
@@ -22,6 +27,9 @@ from pathlib import Path
|
||||
|
||||
_handle = None
|
||||
_attempt = 0
|
||||
_run_id = ""
|
||||
_db_on = True
|
||||
_conn = None
|
||||
|
||||
|
||||
def close() -> None:
|
||||
@@ -31,13 +39,62 @@ def close() -> None:
|
||||
|
||||
def _reset() -> None:
|
||||
"""Close the file and forget the attempt count (tests; a new Run)."""
|
||||
global _handle, _attempt
|
||||
global _handle, _attempt, _run_id, _db_on, _conn
|
||||
try:
|
||||
if _handle:
|
||||
_handle.close()
|
||||
except Exception:
|
||||
pass
|
||||
_handle, _attempt = None, 0
|
||||
try:
|
||||
if _conn is not None:
|
||||
_conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
_handle, _attempt, _run_id, _db_on, _conn = None, 0, "", True, None
|
||||
|
||||
|
||||
def _sql_insert(run_id, at, kind, body) -> None:
|
||||
"""The real copy: one INSERT on a connection kept for the Run."""
|
||||
global _conn
|
||||
from .. import settings as cfg
|
||||
# A test that points LOGS at a temp folder never writes the real table.
|
||||
if not cfg.RESULTS_DB or \
|
||||
Path(cfg.LOGS).resolve() != (Path(cfg.ROOT) / "Logs").resolve():
|
||||
return
|
||||
if _conn is None:
|
||||
import pyodbc
|
||||
_conn = pyodbc.connect(cfg.RESULTS_DB_CONN, timeout=10,
|
||||
autocommit=True)
|
||||
_conn.cursor().execute(
|
||||
"INSERT INTO dbo.CV_RunEvent (run_id, at, kind, body) "
|
||||
"VALUES (?, ?, ?, ?)", [run_id, at, kind, body])
|
||||
|
||||
|
||||
_db_writer = _sql_insert
|
||||
|
||||
|
||||
def _real_run(log_path) -> bool:
|
||||
"""A Run in the real Logs folder: a test's log in a temp folder never
|
||||
reaches the database, whatever this PC's `.env` says."""
|
||||
try:
|
||||
from .. import settings as cfg
|
||||
return Path(log_path).resolve().parent == Path(cfg.LOGS).resolve()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _copy(run_id: str, kind: str, line: str) -> None:
|
||||
"""The line to the database too. Never raises."""
|
||||
global _db_on
|
||||
if not _db_on or not run_id:
|
||||
return
|
||||
try:
|
||||
_db_writer(run_id, datetime.now(), kind, line)
|
||||
except Exception as exc:
|
||||
_db_on = False
|
||||
why = str(exc).splitlines()[0] if str(exc) else type(exc).__name__
|
||||
print(f"[EVENTS] Not copied to the database for the rest of this "
|
||||
f"run: {why}")
|
||||
|
||||
|
||||
def run_id_of(log_path) -> str:
|
||||
@@ -77,8 +134,9 @@ def _line(kind: str, fields: dict) -> str:
|
||||
|
||||
def start(log_path, mode: str, claim_limit=None, test_sheet: str = "") -> None:
|
||||
"""Open this Run's events file and write `run_start`."""
|
||||
global _handle
|
||||
global _handle, _run_id
|
||||
_reset()
|
||||
_run_id = run_id_of(log_path) if _real_run(log_path) else ""
|
||||
try:
|
||||
_handle = open(path_for(log_path), "a", encoding="utf-8",
|
||||
buffering=1)
|
||||
@@ -98,20 +156,25 @@ def emit(kind: str, **fields) -> None:
|
||||
"""Write one event. Never raises."""
|
||||
if _handle is None:
|
||||
return
|
||||
line = _line(kind, fields)
|
||||
try:
|
||||
_handle.write(_line(kind, fields) + "\n")
|
||||
_handle.write(line + "\n")
|
||||
_handle.flush()
|
||||
except Exception:
|
||||
pass
|
||||
_copy(_run_id, kind, line)
|
||||
|
||||
|
||||
def append_to(log_path, kind: str, **fields) -> None:
|
||||
"""Add one event to another Run's file — the Portal, after a Stop."""
|
||||
line = _line(kind, fields)
|
||||
try:
|
||||
with open(path_for(log_path), "a", encoding="utf-8") as fh:
|
||||
fh.write(_line(kind, fields) + "\n")
|
||||
fh.write(line + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
if _real_run(log_path):
|
||||
_copy(run_id_of(log_path), kind, line)
|
||||
|
||||
|
||||
def warning(code: str, message: str, **fields) -> None:
|
||||
|
||||
Reference in new issue
Block a user