feat: always write a fixed-name copy of server/sud logs, use .json for sud logs

Alongside the dated log files, ServerLogTask/SudLogTask now also write
(and overwrite) a fixed-name copy on every write() - logs/log_latest.json
and logs/log_latest_sud.json - so a dashboard/tail-style consumer can
point at one unchanging path instead of tracking the current run's
timestamped filename.

Also switches SudLogTask's own dated/latest filenames from .log to .json,
matching what they've always actually contained. This incidentally fixes
utils/analyze_log.py's two-argument CLI form, which already assumed a
.json extension for sud logs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JFDeoTKBDkXRhPfnyDEoBt
This commit is contained in:
2026-07-01 20:23:16 +02:00
co-authored by Claude Sonnet 5
parent da7e954b7c
commit a495e758fd
3 changed files with 24 additions and 6 deletions
+14 -3
View File
@@ -89,6 +89,13 @@ class ServerLogTask(ATask):
def _filename(self):
return 'log_{}.json'.format(self._run_id)
def _latest_filename(self):
"""Fixed (no-date) name written alongside the dated one on every
write() - always overwritten, so a dashboard/tail-style consumer
can point at one unchanging path instead of tracking the current
run's own timestamped filename."""
return 'log_latest.json'
def _extra_log_data(self):
"""Hook for subclasses (SudLogTask) to contribute additional keys
to write()'s output - see there."""
@@ -105,7 +112,6 @@ class ServerLogTask(ATask):
return
os.makedirs(self.path, exist_ok=True)
filename = self._filename()
path = os.path.join(self.path, filename)
log_data = {'Name': self._log_name()}
if self.dt is not None:
log_data['dt'] = self.dt
@@ -122,6 +128,11 @@ class ServerLogTask(ATask):
log_data['PlantParams'] = self._param_events
log_data.update(self._extra_log_data())
log_data['Samples'] = self._samples
with open(path, 'w') as f:
json.dump(log_data, f, indent='\t')
# Written to both the dated filename (this run/session's own
# permanent record) and a fixed, always-overwritten one (see
# _latest_filename()) - same content, so a consumer wanting
# "whatever's most current" never has to track the dated name.
for name in (filename, self._latest_filename()):
with open(os.path.join(self.path, name), 'w') as f:
json.dump(log_data, f, indent='\t')
print('Server log: wrote {} samples to {}'.format(len(self._samples), filename))