From 9c36363b011d34eca500196e1280af49f86eb61d Mon Sep 17 00:00:00 2001 From: goodboy Date: Fri, 29 May 2026 19:17:55 -0400 Subject: [PATCH] Fix `get_logger()` collapse of nested sub-pkgs Strip the trailing `pkg_path` token ONLY when it duplicates the caller's leaf-*module* name (which the console header already shows via `{filename}`), instead of blindly dropping the last token. This keeps genuine, possibly-*nested* sub-PACKAGE parts addressable as their own sub-loggers. - detect a true leaf-mod by comparing the caller's `__name__` vs `__package__` (a pkg `__init__` has them equal -> its trailing token is a real sub-pkg, NOT a leaf to strip). - `name='devx.debug'` now -> `tractor.devx.debug`, DISTINCT from a bare `devx` -> `tractor.devx`; the old unconditional `pkg_path = subpkg_path` collapsed both to `tractor.devx` and silently broke per-sub-pkg level control via the logging-spec. - `get_logger(__name__)` leaf-strip still works (cosmetic, bc the leaf-mod is in the `{filename}` header field). Also, - update the `LogSpec` caveat: sub-PACKAGE granularity now addressable at ANY depth; leaf *modules* intentionally aren't (they're the `{filename}`); top-level mods (eg. `to_asyncio`) still emit on the root logger. - adjust `test_root_pkg_not_duplicated_in_logger_name` to the new literal explicit-`name` contract (no leaf-collapse). (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code --- tests/test_log_sys.py | 17 +++++++++- tractor/log.py | 79 ++++++++++++++++++++++++++++++------------- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/tests/test_log_sys.py b/tests/test_log_sys.py index 87328007..1c74ba1e 100644 --- a/tests/test_log_sys.py +++ b/tests/test_log_sys.py @@ -20,6 +20,16 @@ def test_root_pkg_not_duplicated_in_logger_name(): a common `.< >` prefix, ensure that it is not duplicated in the child's `StackLevelAdapter.name: str`. + Also pins the explicit-`name` contract: an explicitly passed + dotted `name` is treated as a *literal* sub-logger path and is + NOT leaf-collapsed. The leaf-module is only dropped when the + trailing token duplicates the *caller's own* `__name__` leaf (the + `{filename}` field) — see `test_implicit_mod_name_applied_for_child` + for that (auto-naming) path. This is what keeps a real (possibly + nested) sub-PACKAGE like `subpkg.mod` -> `devx.debug` addressable + by the `tractor.log` logging-spec, instead of collapsing to its + parent. + ''' project_name: str = 'pylib' pkg_path: str = 'pylib.subpkg.mod' @@ -38,8 +48,13 @@ def test_root_pkg_not_duplicated_in_logger_name(): ) assert proj_log is not sublog + # the root pkg-name appears exactly once (no `pylib.pylib...`) assert sublog.name.count(proj_log.name) == 1 - assert 'mod' not in sublog.name + # explicit dotted `name` is preserved literally (NOT collapsed); + # the trailing token survives since it's not the *caller's* own + # leaf-module (`test_log_sys`), so this is treated as a literal + # sub-pkg path. + assert sublog.name == f'{project_name}.subpkg.mod' def test_implicit_mod_name_applied_for_child( diff --git a/tractor/log.py b/tractor/log.py index d11901a7..95f313a4 100644 --- a/tractor/log.py +++ b/tractor/log.py @@ -543,21 +543,45 @@ def get_logger( # only includes the first 2 sub-pkg name-tokens in the # child-logger's name; the colored "pkg-namespace" header # will then correctly show the same value as `name`. + # + # XXX, strip the trailing `pkg_path` token ONLY when it + # duplicates the caller's leaf-*module* name — which the + # console header already renders via its `{filename}` field. + # We compare against the caller module's `__name__`/ + # `__package__` (rather than blindly dropping the last token) + # so genuine, possibly-*nested* sub-PACKAGE components stay + # addressable as their own sub-loggers: + # + # - `name='trionics._broadcast'` (a leaf-module, from a + # `get_logger(__name__)`-style call) -> `tractor.trionics` + # (leaf dropped; `_broadcast.py` is in the header). + # - `name='devx.debug'` (a real sub-PACKAGE, whether + # auto-derived from a module's `__package__` or passed + # explicitly by a logging-spec) -> `tractor.devx.debug`, + # DISTINCT from a bare `devx` -> `tractor.devx`. + # + # The previous unconditional `pkg_path = subpkg_path` also ate + # the deepest sub-pkg, collapsing `devx.debug` -> `tractor.devx` + # and silently breaking per-sub-pkg level control via the + # logging-spec; see `tractor.log.LogSpec`/`apply_logspec()`. + caller_leaf_mod: str|None = None + if (caller_mod := get_caller_mod()): + cmod_name: str = getattr(caller_mod, '__name__', '') or '' + cmod_pkg: str = getattr(caller_mod, '__package__', '') or '' + # a leaf-*module* has `__name__ != __package__`; a package + # `__init__` has them equal (so its trailing token is a + # real sub-pkg, NOT a leaf-module-filename to strip). + if cmod_name and cmod_name != cmod_pkg: + caller_leaf_mod = cmod_name.rpartition('.')[2] + if ( - # XXX, TRY to remove duplication cases - # which get warn-logged on below! - ( - # when, subpkg_path == pkg_path - subpkg_path - and - rname == pkg_name - ) - # ) or ( - # # when, pkg_path == leaf_mod - # pkg_path - # and - # leaf_mod == pkg_path - # ) + subpkg_path + and + rname == pkg_name + and + # only collapse when the trailing token IS the caller's + # leaf-module (i.e. the `{filename}` already shows it). + leaf_mod == caller_leaf_mod ): pkg_path = subpkg_path @@ -724,18 +748,25 @@ def get_console_log( # - 'sub:info,x:cancel' -> per-sub-logger levels; each `` is # RELATIVE to `pkg_name` (must NOT include # the `pkg_name` token itself), eg. -# 'devx:runtime,trionics:cancel'. +# 'devx.debug:runtime,trionics:cancel'. # -# !GRANULARITY CAVEAT! sub-logger names are matched at the -# `pkg_name.` *logger* level, which (per `get_logger()`'s -# name-derivation) is effectively the *sub-package* granularity: -# - leaf module-names are stripped, so 'devx.debug' collapses to -# the same `tractor.devx` logger as a bare 'devx' (you CANNOT -# isolate a single leaf-module's emits from its sub-pkg). +# !GRANULARITY! sub-logger names match at the `pkg_name.` +# *logger* level — which (per `get_logger()`'s name-derivation) is +# *sub-PACKAGE* granularity, addressable at ANY nesting depth: +# - 'devx.debug' -> the `tractor.devx.debug` logger, DISTINCT from a +# bare 'devx' -> `tractor.devx` (its parent). Setting `devx` also +# gates `devx.debug` via normal stdlib level-inheritance unless the +# child sets its own level. +# - leaf *modules* are intentionally NOT individually addressable: +# `get_logger()` drops the leaf module-name from the logger key +# since the console header already renders it via `{filename}`, so +# every module in a (sub-)pkg shares that pkg's logger. Per-leaf +# level control would need a record-filter (see follow-up notes: +# `ai/tooling-todos/logspec_leaf_module_granularity_route_b.md`). # - top-level lib modules (eg. `tractor.to_asyncio`) emit under the -# *root* `pkg_name` logger (their `__package__` IS `pkg_name`), -# so a 'to_asyncio:' filter targets a phantom child that -# nothing emits to -> no-op. Use the root-level form for those. +# *root* `pkg_name` logger (their `__package__` IS `pkg_name`), so +# a 'to_asyncio:' entry targets a phantom child that nothing +# emits to -> no-op. Use the bare-level/root form for those. LogSpec = str|bool