fix(logs): Implement ultra-robust path resolution and retry logic in LogPruner
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from src.log_registry import LogRegistry
|
from src.log_registry import LogRegistry
|
||||||
|
|
||||||
@@ -49,15 +50,31 @@ class LogPruner:
|
|||||||
if not session_path:
|
if not session_path:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Robust path resolution
|
# RESOLUTION STRATEGY:
|
||||||
if os.path.isabs(session_path):
|
# 1. Try as-is (absolute or relative to project root)
|
||||||
resolved_path = session_path
|
# 2. Try as a sub-directory of self.logs_dir (e.g. logs/sessions/session_id)
|
||||||
else:
|
# 3. Try relative to parent of logs_dir if it starts with 'logs/'
|
||||||
# Always resolve relative to project root
|
|
||||||
resolved_path = os.path.abspath(os.path.join(project_root, session_path))
|
|
||||||
|
|
||||||
if not os.path.isdir(resolved_path):
|
candidates = []
|
||||||
sys.stderr.write(f"[LogPruner] Skipping {session_id}: directory not found at {resolved_path}\n")
|
if os.path.isabs(session_path):
|
||||||
|
candidates.append(session_path)
|
||||||
|
else:
|
||||||
|
candidates.append(os.path.abspath(os.path.join(project_root, session_path)))
|
||||||
|
candidates.append(os.path.abspath(os.path.join(self.logs_dir, session_id)))
|
||||||
|
candidates.append(os.path.abspath(os.path.join(self.logs_dir, os.path.basename(session_path))))
|
||||||
|
|
||||||
|
resolved_path = None
|
||||||
|
for cand in candidates:
|
||||||
|
if os.path.isdir(cand):
|
||||||
|
resolved_path = cand
|
||||||
|
break
|
||||||
|
|
||||||
|
if not resolved_path:
|
||||||
|
# If we can't find it, we still remove it from the registry if it's "empty"
|
||||||
|
# so it stops cluttering the UI.
|
||||||
|
sys.stderr.write(f"[LogPruner] Could not find directory for {session_id} in candidates. Removing registry entry.\n")
|
||||||
|
if session_id in self.log_registry.data:
|
||||||
|
del self.log_registry.data[session_id]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Calculate total size of files in the directory
|
# Calculate total size of files in the directory
|
||||||
@@ -71,11 +88,26 @@ class LogPruner:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Prune if the total size is less than threshold
|
# Prune if the total size is less than threshold
|
||||||
# (The user requested always removing empty logs, get_old_non_whitelisted_sessions already handles the 'empty' check)
|
|
||||||
if total_size < (min_size_kb * 1024) or total_size == 0:
|
if total_size < (min_size_kb * 1024) or total_size == 0:
|
||||||
try:
|
try:
|
||||||
sys.stderr.write(f"[LogPruner] Removing {session_id} at {resolved_path} (Size: {total_size} bytes)\n")
|
sys.stderr.write(f"[LogPruner] Removing {session_id} at {resolved_path} (Size: {total_size} bytes)\n")
|
||||||
shutil.rmtree(resolved_path)
|
|
||||||
|
# Windows specific: sometimes files are locked.
|
||||||
|
# We try a few times with small delays.
|
||||||
|
def remove_readonly(func, path, excinfo):
|
||||||
|
os.chmod(path, 0o777)
|
||||||
|
func(path)
|
||||||
|
|
||||||
|
for attempt in range(3):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(resolved_path, onerror=remove_readonly)
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
if attempt < 2:
|
||||||
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# Also remove from registry to keep it in sync
|
# Also remove from registry to keep it in sync
|
||||||
if session_id in self.log_registry.data:
|
if session_id in self.log_registry.data:
|
||||||
del self.log_registry.data[session_id]
|
del self.log_registry.data[session_id]
|
||||||
|
|||||||
Reference in New Issue
Block a user