console logs ROTATE instead of truncate -- tonight's evidence loss cannot repeat

Two destroyers, both in btoperator.py:
  * Start Session zeroed operator_relay.log -- took the whole 23:08 session
    (the reservoir-crash round's relay trace) minutes after it happened;
  * Launch local instances os.remove()'d operator_N.log -- took the pod's
    [crash] self-report block before it could be copied.

Both now rotate the previous file to <name>.1 (btrelay_park's pattern: drop
the old .1, os.replace, never block on a locked file).  One generation of
history is exactly what post-mortems needed twice tonight.  All four console
suites pass; picks up on the console's next restart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-27 00:13:06 -05:00
co-authored by Claude Opus 5
parent b7e4837738
commit becaddb662
+21 -8
View File
@@ -36,6 +36,24 @@ from PySide6.QtWidgets import (
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.abspath(os.path.join(HERE, ".."))
CONTENT = os.path.join(REPO, "content")
def rotate_log(path):
"""Keep the PREVIOUS run's log as <path>.1 instead of destroying it.
2026-07-26 games night: the crash self-report block and the whole 23:08
session's relay log were both lost to truncation minutes after they were
written -- Start Session zeroed operator_relay.log and Launch-instances
os.remove()'d the pod log. One generation of history is cheap insurance.
"""
try:
if os.path.exists(path):
prev = path + ".1"
if os.path.exists(prev):
os.remove(prev)
os.replace(path, prev)
except OSError:
pass # a locked log must never block a session
DEFAULT_EXE = os.path.join(REPO, "build", "Release", "btl4.exe")
DEFAULT_EGG = os.path.join(CONTENT, "OPERATOR.EGG")
BTCONSOLE = os.path.join(HERE, "btconsole.py")
@@ -879,10 +897,8 @@ class Operator(QMainWindow):
for tag in tags:
host, _, port = tag.rpartition(":")
args.append("%s:%d" % (host, int(port) - 1))
try: # fresh relay log per session
open(os.path.join(CONTENT, "operator_relay.log"), "w").close()
except OSError:
pass
# fresh relay log per session -- the OLD session survives as .1
rotate_log(os.path.join(CONTENT, "operator_relay.log"))
self.console_proc = QProcess(self)
self.console_proc.setProcessChannelMode(QProcess.MergedChannels)
self.console_proc.readyReadStandardOutput.connect(self._console_output)
@@ -1284,10 +1300,7 @@ class Operator(QMainWindow):
# chain (each generation used to TRUNCATE the log -- the night-2
# crash loop left a 2-line file). Fresh file per launch press.
log_name = "operator_%d.log" % (r + 1)
try:
os.remove(os.path.join(CONTENT, log_name))
except OSError:
pass
rotate_log(os.path.join(CONTENT, log_name))
env.insert("BT_LOG", log_name)
env.insert("BT_LOG_APPEND", "1")
proc = QProcess(self)