← back to Exo
standardise logs (#1442)
1699fcfb9f317b2738876009aef84cf20521ab7e · 2026-02-10 19:13:25 +0000 · rltakashige
## Motivation
Standardises exo.log and event_log
## Changes
- exo.log and exo.log.zst are now in a exo_log directory.
- event_log is now timestamped and not numbered. The timestamps can be
sorted as they are YYYY-MM-DD-HH-MM
## Test Plan
### Manual Testing
Nothing crashes.
Files touched
M src/exo/master/event_log.pyM src/exo/master/tests/test_event_log.pyM src/exo/shared/constants.py
Diff
commit 1699fcfb9f317b2738876009aef84cf20521ab7e
Author: rltakashige <rl.takashige@gmail.com>
Date: Tue Feb 10 19:13:25 2026 +0000
standardise logs (#1442)
## Motivation
Standardises exo.log and event_log
## Changes
- exo.log and exo.log.zst are now in a exo_log directory.
- event_log is now timestamped and not numbered. The timestamps can be
sorted as they are YYYY-MM-DD-HH-MM
## Test Plan
### Manual Testing
Nothing crashes.
---
src/exo/master/event_log.py | 30 ++++++++-------------
src/exo/master/tests/test_event_log.py | 48 +++++++++++++++++++++-------------
src/exo/shared/constants.py | 3 ++-
3 files changed, 43 insertions(+), 38 deletions(-)
diff --git a/src/exo/master/event_log.py b/src/exo/master/event_log.py
index 1ceeb86f..f7770113 100644
--- a/src/exo/master/event_log.py
+++ b/src/exo/master/event_log.py
@@ -2,6 +2,7 @@ import contextlib
import json
from collections import OrderedDict
from collections.abc import Iterator
+from datetime import datetime, timezone
from io import BufferedRandom, BufferedReader
from pathlib import Path
@@ -159,35 +160,26 @@ class DiskEventLog:
elif self._active_path.exists():
self._active_path.unlink()
- @staticmethod
- def _archive_path(directory: Path, n: int) -> Path:
- return directory / f"events.{n}.bin.zst"
-
@staticmethod
def _rotate(source: Path, directory: Path) -> None:
- """Compress source into a numbered archive, shifting older archives.
+ """Compress source into a timestamped archive.
- Keeps at most ``_MAX_ARCHIVES`` compressed copies. The most recent
- archive is always ``events.1.bin.zst``; older ones are shifted up
- (2, 3, …) and the oldest beyond the limit is deleted.
+ Keeps at most ``_MAX_ARCHIVES`` compressed copies. Oldest beyond
+ the limit are deleted.
"""
try:
- # Shift existing archives
- oldest = DiskEventLog._archive_path(directory, _MAX_ARCHIVES)
- with contextlib.suppress(FileNotFoundError):
- oldest.unlink()
- for i in range(_MAX_ARCHIVES - 1, 0, -1):
- current = DiskEventLog._archive_path(directory, i)
- if current.exists():
- current.rename(DiskEventLog._archive_path(directory, i + 1))
-
- # Compress source into slot 1
- dest = DiskEventLog._archive_path(directory, 1)
+ stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d_%H-%M-%S_%f")
+ dest = directory / f"events.{stamp}.bin.zst"
compressor = zstandard.ZstdCompressor()
with open(source, "rb") as f_in, open(dest, "wb") as f_out:
compressor.copy_stream(f_in, f_out)
source.unlink()
logger.info(f"Rotated event log: {source} -> {dest}")
+
+ # Prune oldest archives beyond the limit
+ archives = sorted(directory.glob("events.*.bin.zst"))
+ for old in archives[:-_MAX_ARCHIVES]:
+ old.unlink()
except Exception as e:
logger.opt(exception=e).warning(f"Failed to rotate event log {source}")
# Clean up the source even if compression fails
diff --git a/src/exo/master/tests/test_event_log.py b/src/exo/master/tests/test_event_log.py
index 9dcd09e8..ffb4fb8e 100644
--- a/src/exo/master/tests/test_event_log.py
+++ b/src/exo/master/tests/test_event_log.py
@@ -66,26 +66,31 @@ def test_empty_log(log_dir: Path):
log.close()
+def _archives(log_dir: Path) -> list[Path]:
+ return sorted(log_dir.glob("events.*.bin.zst"))
+
+
def test_rotation_on_close(log_dir: Path):
log = DiskEventLog(log_dir)
log.append(TestEvent())
log.close()
active = log_dir / "events.bin"
- archive = log_dir / "events.1.bin.zst"
assert not active.exists()
- assert archive.exists()
- assert archive.stat().st_size > 0
+
+ archives = _archives(log_dir)
+ assert len(archives) == 1
+ assert archives[0].stat().st_size > 0
def test_rotation_on_construction_with_stale_file(log_dir: Path):
log_dir.mkdir(parents=True, exist_ok=True)
- active = log_dir / "events.bin"
- active.write_bytes(b"stale data")
+ (log_dir / "events.bin").write_bytes(b"stale data")
log = DiskEventLog(log_dir)
- archive = log_dir / "events.1.bin.zst"
- assert archive.exists()
+ archives = _archives(log_dir)
+ assert len(archives) == 1
+ assert archives[0].exists()
assert len(log) == 0
log.close()
@@ -97,19 +102,19 @@ def test_empty_log_no_archive(log_dir: Path):
log.close()
active = log_dir / "events.bin"
- archive = log_dir / "events.1.bin.zst"
+
assert not active.exists()
- assert not archive.exists()
+ assert _archives(log_dir) == []
def test_close_is_idempotent(log_dir: Path):
log = DiskEventLog(log_dir)
log.append(TestEvent())
log.close()
+ archive = _archives(log_dir)
log.close() # should not raise
- archive = log_dir / "events.1.bin.zst"
- assert archive.exists()
+ assert _archives(log_dir) == archive
def test_successive_sessions(log_dir: Path):
@@ -118,7 +123,7 @@ def test_successive_sessions(log_dir: Path):
log1.append(TestEvent())
log1.close()
- assert (log_dir / "events.1.bin.zst").exists()
+ first_archive = _archives(log_dir)[-1]
log2 = DiskEventLog(log_dir)
log2.append(TestEvent())
@@ -126,18 +131,25 @@ def test_successive_sessions(log_dir: Path):
log2.close()
# Session 1 archive shifted to slot 2, session 2 in slot 1
- assert (log_dir / "events.1.bin.zst").exists()
- assert (log_dir / "events.2.bin.zst").exists()
+ second_archive = _archives(log_dir)[-1]
+ should_be_first_archive = _archives(log_dir)[-2]
+
+ assert first_archive.exists()
+ assert second_archive.exists()
+ assert first_archive != second_archive
+ assert should_be_first_archive == first_archive
def test_rotation_keeps_at_most_5_archives(log_dir: Path):
"""After 7 sessions, only the 5 most recent archives should remain."""
+ all_archives: list[Path] = []
for _ in range(7):
log = DiskEventLog(log_dir)
log.append(TestEvent())
log.close()
+ all_archives.append(_archives(log_dir)[-1])
- for i in range(1, 6):
- assert (log_dir / f"events.{i}.bin.zst").exists()
- assert not (log_dir / "events.6.bin.zst").exists()
- assert not (log_dir / "events.7.bin.zst").exists()
+ for old in all_archives[:2]:
+ assert not old.exists()
+ for recent in all_archives[2:]:
+ assert recent.exists()
diff --git a/src/exo/shared/constants.py b/src/exo/shared/constants.py
index 179716af..78781209 100644
--- a/src/exo/shared/constants.py
+++ b/src/exo/shared/constants.py
@@ -43,7 +43,8 @@ DASHBOARD_DIR = (
)
# Log files (data/logs or cache)
-EXO_LOG = EXO_CACHE_HOME / "exo.log"
+EXO_LOG_DIR = EXO_CACHE_HOME / "exo_log"
+EXO_LOG = EXO_LOG_DIR / "exo.log"
EXO_TEST_LOG = EXO_CACHE_HOME / "exo_test.log"
# Identity (config)
← 009b43c6 add log rotation for .exo/exo.log (#1438)
·
back to Exo
·
Send all exo logs (#1439) 43728b20 →