Record the source of loaded modules for auditability

Now that installed module packages load automatically, record where every
module came from:

- --list-modules groups the available modules by source, one line per
  source with the modules comma-separated: MVT itself with its version,
  each installed package with its version and VCS commit when recorded
  (PEP 610 direct_url.json), and each --load-module/MVT_CUSTOM_MODULES
  file with its SHA-256 hash.
- Commands log one line per module source with its version or hash and
  the modules loaded from it, so command.log records exactly which
  modules ran and where they came from.
- Make init_logging() idempotent: a loaded module package importing an
  MVT CLI module would previously add a second console handler and
  duplicate every console log line.
This commit is contained in:
Donncha Ó Cearbhaill
2026-08-19 21:30:07 +02:00
parent cb25137423
commit e77008b49a
7 changed files with 271 additions and 4 deletions
+19
View File
@@ -8,6 +8,7 @@ import logging
import os
from datetime import datetime
from mvt.common.log import MVTLogHandler
from mvt.common.utils import (
CustomJSONEncoder,
convert_datetime_to_iso,
@@ -16,6 +17,7 @@ from mvt.common.utils import (
convert_unix_to_utc_datetime,
generate_hashes_from_path,
get_sha256_from_file_path,
init_logging,
)
from ..utils import get_artifact_folder
@@ -103,3 +105,20 @@ class TestCustomJSONEncoder:
json.dumps({"name": "".encode()}, cls=CustomJSONEncoder)
== '{"name": "\\u5bb6"}'
)
class TestInitLogging:
def test__init_logging_is_idempotent(self):
# Loaded module packages may import an MVT CLI module, which calls
# init_logging() again at import time. A second call must not add
# a duplicate console handler.
log = logging.getLogger("mvt")
init_logging()
handler_count = sum(
isinstance(handler, MVTLogHandler) for handler in log.handlers
)
init_logging()
assert (
sum(isinstance(handler, MVTLogHandler) for handler in log.handlers)
== handler_count
)