diff --git a/changelog.rst b/changelog.rst index 96eefd74..7b1c3a13 100644 --- a/changelog.rst +++ b/changelog.rst @@ -18,6 +18,8 @@ Internal: * Use github trusted publisher for pypi release * Update dev requirements and replace requirements-dev.txt with pyproject.toml * Use ruff instead of black +* Implement daily log rotation at midnight with 30-day retention using TimedRotatingFileHandler +* Change default log location to /var/log/pgcli/pgcli.log (with automatic fallback to ~/.config/pgcli/log if no permissions) Bug fixes: ---------- diff --git a/pgcli/main.py b/pgcli/main.py index 0b4b64f5..3e62497b 100644 --- a/pgcli/main.py +++ b/pgcli/main.py @@ -9,6 +9,7 @@ import sys import traceback import logging +import logging.handlers import threading import shutil import functools @@ -537,7 +538,18 @@ def write_to_file(self, pattern, **_): def initialize_logging(self): log_file = self.config["main"]["log_file"] if log_file == "default": - log_file = config_location() + "log" + # Try /var/log/pgcli first, fallback to user config dir if no permissions + default_system_log = "/var/log/pgcli/pgcli.log" + try: + os.makedirs(os.path.dirname(default_system_log), exist_ok=True) + # Test if we can write to /var/log/pgcli + with open(default_system_log, "a"): + pass + log_file = default_system_log + except (OSError, PermissionError): + # Fallback to user's config directory if no system permissions + log_file = config_location() + "log" + ensure_dir_exists(log_file) log_level = self.config["main"]["log_level"] @@ -546,7 +558,24 @@ def initialize_logging(self): if log_level.upper() == "NONE": handler = logging.NullHandler() else: - handler = logging.FileHandler(os.path.expanduser(log_file)) + # Use TimedRotatingFileHandler for daily log rotation + # Rotates at midnight, keeps 30 days of logs + expanded_log_file = os.path.expanduser(log_file) + + # Remove .log extension if present to avoid pgcli.log.log pattern + base_log_file = expanded_log_file + if base_log_file.endswith('.log'): + base_log_file = base_log_file[:-4] + + handler = logging.handlers.TimedRotatingFileHandler( + base_log_file, + when='midnight', + interval=1, + backupCount=30, + encoding='utf-8' + ) + # Format: pgcli.YYYY-MM-DD.log + handler.suffix = ".%Y-%m-%d.log" level_map = { "CRITICAL": logging.CRITICAL, diff --git a/pgcli/pgclirc b/pgcli/pgclirc index 63ccdaf3..a789c0f1 100644 --- a/pgcli/pgclirc +++ b/pgcli/pgclirc @@ -66,9 +66,12 @@ generate_aliases = False alias_map_file = # log_file location. -# In Unix/Linux: ~/.config/pgcli/log -# In Windows: %USERPROFILE%\AppData\Local\dbcli\pgcli\log -# %USERPROFILE% is typically C:\Users\{username} +# Default behavior: +# - Tries /var/log/pgcli/pgcli.log first (if running with permissions) +# - Falls back to ~/.config/pgcli/log (Unix/Linux) or %USERPROFILE%\AppData\Local\dbcli\pgcli\log (Windows) +# Log rotation: Daily at midnight, keeps 30 days of logs +# Format: pgcli.YYYY-MM-DD.log +# You can specify a custom path here if needed log_file = default # keyword casing preference. Possible values: "lower", "upper", "auto"