This is an automated email from the ASF dual-hosted git repository. sbp pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tooling-trusted-release.git
The following commit(s) were added to refs/heads/main by this push: new c0f4019 Add a basic audit log, for check result ignore events only c0f4019 is described below commit c0f40192c4e125e52a326b5462fcdffe7baa8c09 Author: Sean B. Palmer <s...@miscoranda.com> AuthorDate: Fri Aug 1 17:01:05 2025 +0100 Add a basic audit log, for check result ignore events only --- atr/config.py | 2 ++ atr/server.py | 22 +++++++++++++++++++++- atr/storage/__init__.py | 12 +++++++++++- atr/storage/writers/checks.py | 8 +++++--- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/atr/config.py b/atr/config.py index 3acc85a..60f5500 100644 --- a/atr/config.py +++ b/atr/config.py @@ -61,6 +61,7 @@ class AppConfig: # TODO: We need to get Puppet to check SVN out initially, or do it manually SVN_STORAGE_DIR = os.path.join(STATE_DIR, "svn") SQLITE_DB_PATH = decouple.config("SQLITE_DB_PATH", default="atr.db") + AUDIT_LOG_FILE = os.path.join(STATE_DIR, "audit.log") # Apache RAT configuration APACHE_RAT_JAR_PATH = decouple.config("APACHE_RAT_JAR_PATH", default="/opt/tools/apache-rat-0.16.1.jar") @@ -131,6 +132,7 @@ def get() -> type[AppConfig]: (config.FINISHED_STORAGE_DIR, "FINISHED_STORAGE_DIR"), (config.UNFINISHED_STORAGE_DIR, "UNFINISHED_STORAGE_DIR"), (config.SVN_STORAGE_DIR, "SVN_STORAGE_DIR"), + (config.AUDIT_LOG_FILE, "AUDIT_LOG_FILE"), ] relative_paths = [ (config.SQLITE_DB_PATH, "SQLITE_DB_PATH"), diff --git a/atr/server.py b/atr/server.py index e40bbad..f9add42 100644 --- a/atr/server.py +++ b/atr/server.py @@ -209,7 +209,27 @@ def app_setup_logging(app: base.QuartApp, config_mode: config.Mode, app_config: handlers=[rich_logging.RichHandler(rich_tracebacks=True, show_time=False)], ) - # enable debug output for atr.* in DEBUG mode + # Configure dedicated audit logger + try: + audit_handler = logging.FileHandler( + app_config.AUDIT_LOG_FILE, + encoding="utf-8", + mode="a", + ) + audit_handler.setFormatter( + logging.Formatter( + "%(asctime)s %(message)s", + datefmt="%Y-%m-%dT%H:%M:%SZ", + ) + ) + audit_logger = logging.getLogger("atr.storage.audit") + audit_logger.setLevel(logging.INFO) + audit_logger.addHandler(audit_handler) + audit_logger.propagate = False + except Exception: + logging.getLogger(__name__).exception("Failed to configure audit logger") + + # Enable debug output for atr.* in DEBUG mode if config_mode == config.Mode.Debug: logging.getLogger(atr.__name__).setLevel(logging.DEBUG) diff --git a/atr/storage/__init__.py b/atr/storage/__init__.py index edb2176..4125d6f 100644 --- a/atr/storage/__init__.py +++ b/atr/storage/__init__.py @@ -18,6 +18,7 @@ from __future__ import annotations import contextlib +import logging import time from typing import TYPE_CHECKING, Final @@ -42,8 +43,17 @@ VALIDATE_AT_RUNTIME: Final[bool] = True ## Access credentials +def audit(msg: str) -> None: + msg = msg.replace("\n", " / ") + # The atr.log logger should give the same name + # But to be extra sure, we set it manually + logger = logging.getLogger("atr.storage.audit") + logger.info(msg) + + class AccessCredentials: - pass + def audit_worthy_event(self, msg: str) -> None: + audit(msg) class AccessCredentialsRead(AccessCredentials): ... diff --git a/atr/storage/writers/checks.py b/atr/storage/writers/checks.py index 8182dfb..5263744 100644 --- a/atr/storage/writers/checks.py +++ b/atr/storage/writers/checks.py @@ -23,7 +23,6 @@ import datetime import sqlmodel import atr.db as db -import atr.log as log import atr.models.sql as sql import atr.storage as storage @@ -111,15 +110,15 @@ class CommitteeMember(CommitteeParticipant): status=status, message_glob=message_glob, ) - log.info(f"Status {status}") - log.info(f"Adding check result ignore {cri}") self.__data.add(cri) await self.__data.commit() + self.__credentials.audit_worthy_event(f"Added check result ignore {cri}") async def ignore_delete(self, id: int) -> None: via = sql.validate_instrumented_attribute await self.__data.execute(sqlmodel.delete(sql.CheckResultIgnore).where(via(sql.CheckResultIgnore.id) == id)) await self.__data.commit() + self.__credentials.audit_worthy_event(f"Deleted check result ignore {id} by {self.__asf_uid}") async def ignore_update( self, @@ -135,6 +134,8 @@ class CommitteeMember(CommitteeParticipant): cri = await self.__data.get(sql.CheckResultIgnore, id) if cri is None: raise storage.AccessError(f"Ignore {id} not found") + # The updating ASF UID is now responsible for the whole ignore + cri.asf_uid = self.__asf_uid cri.release_glob = release_glob cri.revision_number = revision_number cri.checker_glob = checker_glob @@ -143,3 +144,4 @@ class CommitteeMember(CommitteeParticipant): cri.status = status cri.message_glob = message_glob await self.__data.commit() + self.__credentials.audit_worthy_event(f"Updated check result ignore {cri} by {self.__asf_uid}") --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@tooling.apache.org For additional commands, e-mail: commits-h...@tooling.apache.org