From 089e158da94857e58e6e55c09727b6145db8f8ac Mon Sep 17 00:00:00 2001 From: goodboy Date: Wed, 12 Aug 2026 20:52:47 -0400 Subject: [PATCH] Retain dynamic logger caller discovery Keep the `sys.modules` lookup as the normal fast path, then use `inspect.getmodule()` for unregistered `runpy`, plugin, and `exec()` namespaces. Cover an unregistered module name backed by a real package file and verify implicit logger naming still resolves to that package. Review: PR #478 (goodboy) https://github.com/goodboy/tractor/pull/478#pullrequestreview-4922213201 (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`)) --- tests/test_log_sys.py | 49 +++++++++++++++++++++++++++++++++++++++++++ tractor/log.py | 9 +++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/tests/test_log_sys.py b/tests/test_log_sys.py index 1894bd1f..f4fcee70 100644 --- a/tests/test_log_sys.py +++ b/tests/test_log_sys.py @@ -2,9 +2,11 @@ `tractor.log`-wrapping unit tests. ''' +import importlib import logging from pathlib import Path import shutil +import sys from types import ModuleType import pytest @@ -165,6 +167,53 @@ def test_implicit_mod_name_applied_for_child( assert submod.log.logger in sub_logs +def test_implicit_mod_name_from_unregistered_namespace( + tmp_path: Path, +): + ''' + Preserve implicit logger naming for dynamic module namespaces. + + The fast `sys.modules` caller lookup cannot resolve `runpy`, + plugin-loader, or `exec()` namespaces that are not registered. + Compile a real package file under an unregistered module name so + the rare filename fallback must recover its imported package and + retain the same package-level logger name. + + ''' + pkg_name = 'dynamic_logger_pkg' + pkg_dir = tmp_path / pkg_name + pkg_dir.mkdir() + init_path = pkg_dir / '__init__.py' + init_path.write_text('') + mod_path = pkg_dir / 'plugin.py' + mod_path.write_text('') + + sys.path.insert(0, str(tmp_path)) + try: + importlib.import_module(pkg_name) + namespace = { + '__name__': f'{pkg_name}.unregistered', + '__package__': pkg_name, + 'tractor': tractor, + } + exec( + compile( + 'log = tractor.log.get_logger(' + f'pkg_name={pkg_name!r})', + str(mod_path), + 'exec', + ), + namespace, + ) + dynamic_log = namespace.get('log') + finally: + sys.path.remove(str(tmp_path)) + sys.modules.pop(pkg_name, None) + + assert dynamic_log is not None + assert dynamic_log.name == pkg_name + + def test_io_custom_level_registered(): ''' The `IO`(21) level (registered via `add_log_level()` at diff --git a/tractor/log.py b/tractor/log.py index 01444b38..7cab2b99 100644 --- a/tractor/log.py +++ b/tractor/log.py @@ -463,7 +463,14 @@ def get_logger( if mod_name is None: return None - return sys.modules.get(mod_name) + if caller_mod := sys.modules.get(mod_name): + return caller_mod + + # Preserve caller discovery for `runpy`, plugin loaders, + # and `exec()` namespaces not registered in `sys.modules`. + # Import `inspect` only on this rare fallback path. + from inspect import getmodule + return getmodule(caller_frame) # --- Auto--naming-CASE --- # -------------------------