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:
Misaka_Company
2026-08-06 11:35:16 +08:00
parent c70dac04e8
commit e2631d5dd7
2 changed files with 51 additions and 0 deletions

View File

@@ -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)

View File

@@ -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)