feat(compact): dedicated compact.log with same daily-archive semantics as sync.log
Compact audit trail now writes to logs/compact.log via its own RotatingFileHandler. The sync.compact logger does not propagate to root, so compact messages are isolated. Archive/ rotation happens on next setup_logging call (service restart) via the existing mechanism. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ import time
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from .config import SyncConfig
|
||||
from .logging_setup import setup_compact_log
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,6 +32,9 @@ log = logging.getLogger(__name__)
|
||||
# it supports both .mdb (Jet) and .accdb (ACE) formats.
|
||||
_DAO_PROGID = "DAO.DBEngine.120"
|
||||
|
||||
# Guard: only set up the compact file handler once per process.
|
||||
_compact_log_ready = False
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ result types
|
||||
|
||||
@@ -264,7 +268,15 @@ def compact_files(cfg: SyncConfig,
|
||||
When *dry_run* is True, only checks file accessibility and lock status
|
||||
without modifying anything. Each file is compacted independently; one
|
||||
failure does not abort the run.
|
||||
|
||||
Logs are written to a dedicated ``logs/compact.log`` file (separate from
|
||||
``sync.log``) with the same rotation and daily-archive semantics.
|
||||
"""
|
||||
global _compact_log_ready
|
||||
if not _compact_log_ready:
|
||||
setup_compact_log(cfg.logging)
|
||||
_compact_log_ready = True
|
||||
|
||||
if dry_run:
|
||||
return compact_dry_run(cfg, db_filter=db_filter, file_list=file_list)
|
||||
|
||||
|
||||
@@ -169,3 +169,42 @@ def setup_logging(cfg_dict: dict | None):
|
||||
# Console keeps the short format (fullsync/compare are interactive there).
|
||||
sh.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
|
||||
root.addHandler(sh)
|
||||
|
||||
|
||||
def setup_compact_log(cfg_dict: dict | None):
|
||||
"""Configure a dedicated file handler for the ``sync.compact`` logger.
|
||||
|
||||
Writes to ``logs/compact.log`` with the same rotation and daily-archive
|
||||
semantics as ``sync.log``, but kept in a separate file so compact audit
|
||||
trail is isolated and easy to grep. The logger does NOT propagate to the
|
||||
root logger — compact messages go to ``compact.log`` only (console output
|
||||
is handled by ``main.py``'s ``print()`` calls, not by this handler).
|
||||
"""
|
||||
level = getattr(logging, (cfg_dict or {}).get("level", "INFO")) if cfg_dict else logging.INFO
|
||||
sync_path = (cfg_dict or {}).get("path", "sync.log")
|
||||
log_dir = os.path.dirname(sync_path) or "."
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
compact_path = os.path.join(log_dir, "compact.log")
|
||||
# Archive yesterday's compact.log using the same logic as sync.log.
|
||||
_archive_completed_logs(log_dir, os.path.basename(compact_path))
|
||||
|
||||
cl = logging.getLogger("sync.compact")
|
||||
cl.setLevel(level)
|
||||
cl.propagate = False # do NOT duplicate into sync.log
|
||||
|
||||
# Idempotent: drop existing handlers before re-adding.
|
||||
for old in list(cl.handlers):
|
||||
cl.removeHandler(old)
|
||||
try:
|
||||
old.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
fh = logging.handlers.RotatingFileHandler(
|
||||
compact_path, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
|
||||
)
|
||||
fh.setFormatter(logging.Formatter(
|
||||
"%(asctime)s %(levelname)s [%(name)s] %(message)s"
|
||||
))
|
||||
cl.addHandler(fh)
|
||||
|
||||
Reference in New Issue
Block a user