Update logging configuration to support daily log files and implement log pruning; add guidance in CLAUDE.md and enhance README.md with command usage

This commit is contained in:
2026-06-27 14:04:44 +08:00
parent 4685a0165d
commit 2fc815b788
6 changed files with 326 additions and 18 deletions

View File

@@ -1,23 +1,128 @@
from __future__ import annotations
from datetime import datetime
import logging
from pathlib import Path
import time
from typing import Callable
import nexus_claude_api.config as config
LOCAL_LOG_DIR = Path("logs")
LOCAL_LOG_FILE = LOCAL_LOG_DIR / "nexus-claude-api.log"
LOG_FILE_PREFIX = "nexus-claude-api"
LOG_FILE_SUFFIX = ".log"
LOG_RETENTION_DAYS = 7
LOCAL_LOG_FILE = LOCAL_LOG_DIR / f"{LOG_FILE_PREFIX}.log"
USER_LOG_DIR = config.USER_CONFIG_DIR / "logs"
USER_LOG_FILE = USER_LOG_DIR / "nexus-claude-api.log"
USER_LOG_FILE = USER_LOG_DIR / f"{LOG_FILE_PREFIX}.log"
LOG_FILE = USER_LOG_FILE
LOG_FORMAT = "%(asctime)s %(levelname)s %(name)s: %(message)s"
def configure_logging(*, verbose: bool = False, log_file: Path = LOG_FILE) -> Path:
DateProvider = Callable[[float | None], datetime]
def _local_datetime(timestamp: float | None = None) -> datetime:
if timestamp is None:
return datetime.fromtimestamp(time.time())
return datetime.fromtimestamp(timestamp)
def _dated_log_file(log_dir: Path, current_date: datetime) -> Path:
return log_dir / f"{LOG_FILE_PREFIX}-{current_date:%Y-%m-%d}{LOG_FILE_SUFFIX}"
class DailyLogFileHandler(logging.Handler):
def __init__(
self,
log_file: Path,
*,
date_provider: DateProvider = _local_datetime,
retention_days: int = LOG_RETENTION_DAYS,
encoding: str = "utf-8",
) -> None:
super().__init__()
self.log_dir = log_file.parent
self.date_provider = date_provider
self.retention_days = retention_days
self.encoding = encoding
self.current_date = ""
self.stream = None
self.log_dir.mkdir(parents=True, exist_ok=True)
self._open_for_date(self.date_provider(None))
self._prune_old_logs()
def emit(self, record: logging.LogRecord) -> None:
try:
record_datetime = self.date_provider(record.created)
self._open_for_date(record_datetime)
message = self.format(record)
if self.stream is None:
return
self.stream.write(message + self.terminator)
self.flush()
except Exception:
self.handleError(record)
@property
def baseFilename(self) -> str:
return str(self.current_log_file)
@property
def terminator(self) -> str:
return "\n"
def flush(self) -> None:
if self.stream and not self.stream.closed:
self.stream.flush()
def close(self) -> None:
try:
if self.stream and not self.stream.closed:
self.stream.close()
finally:
self.stream = None
super().close()
def _open_for_date(self, current_datetime: datetime) -> None:
date_text = current_datetime.strftime("%Y-%m-%d")
if date_text == self.current_date and self.stream is not None:
return
if self.stream is not None and not self.stream.closed:
self.stream.close()
self.current_date = date_text
self.current_log_file = _dated_log_file(self.log_dir, current_datetime)
self.stream = self.current_log_file.open("a", encoding=self.encoding)
self._prune_old_logs()
def _prune_old_logs(self) -> None:
dated_logs = []
for path in self.log_dir.glob(f"{LOG_FILE_PREFIX}-*{LOG_FILE_SUFFIX}"):
date_text = path.stem.removeprefix(f"{LOG_FILE_PREFIX}-")
try:
log_date = datetime.strptime(date_text, "%Y-%m-%d").date()
except ValueError:
continue
dated_logs.append((log_date, path))
dated_logs.sort(reverse=True)
for _, path in dated_logs[self.retention_days :]:
path.unlink(missing_ok=True)
def configure_logging(
*,
verbose: bool = False,
log_file: Path = LOG_FILE,
date_provider: DateProvider = _local_datetime,
) -> Path:
log_file = _dated_log_file(log_file.parent, date_provider(None))
log_file.parent.mkdir(parents=True, exist_ok=True)
formatter = logging.Formatter(LOG_FORMAT)
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler = DailyLogFileHandler(log_file, date_provider=date_provider)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
@@ -32,5 +137,5 @@ def configure_logging(*, verbose: bool = False, log_file: Path = LOG_FILE) -> Pa
def resolve_log_file(*, dev: bool = False) -> Path:
if dev:
return LOCAL_LOG_FILE
return config.USER_CONFIG_DIR / "logs" / "nexus-claude-api.log"
return _dated_log_file(LOCAL_LOG_DIR, _local_datetime(None))
return _dated_log_file(config.USER_CONFIG_DIR / "logs", _local_datetime(None))