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

Reply via email to