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
trionics.start_or_cancel
parent
19a77708ba
commit
9c36363b01
|
|
@ -20,6 +20,16 @@ def test_root_pkg_not_duplicated_in_logger_name():
|
||||||
a common `<root_name>.< >` prefix, ensure that it is not
|
a common `<root_name>.< >` prefix, ensure that it is not
|
||||||
duplicated in the child's `StackLevelAdapter.name: str`.
|
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'
|
project_name: str = 'pylib'
|
||||||
pkg_path: str = 'pylib.subpkg.mod'
|
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
|
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 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(
|
def test_implicit_mod_name_applied_for_child(
|
||||||
|
|
|
||||||
|
|
@ -543,21 +543,45 @@ def get_logger(
|
||||||
# only includes the first 2 sub-pkg name-tokens in the
|
# only includes the first 2 sub-pkg name-tokens in the
|
||||||
# child-logger's name; the colored "pkg-namespace" header
|
# child-logger's name; the colored "pkg-namespace" header
|
||||||
# will then correctly show the same value as `name`.
|
# 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 (
|
if (
|
||||||
# XXX, TRY to remove duplication cases
|
subpkg_path
|
||||||
# which get warn-logged on below!
|
and
|
||||||
(
|
rname == pkg_name
|
||||||
# when, subpkg_path == pkg_path
|
and
|
||||||
subpkg_path
|
# only collapse when the trailing token IS the caller's
|
||||||
and
|
# leaf-module (i.e. the `{filename}` already shows it).
|
||||||
rname == pkg_name
|
leaf_mod == caller_leaf_mod
|
||||||
)
|
|
||||||
# ) or (
|
|
||||||
# # when, pkg_path == leaf_mod
|
|
||||||
# pkg_path
|
|
||||||
# and
|
|
||||||
# leaf_mod == pkg_path
|
|
||||||
# )
|
|
||||||
):
|
):
|
||||||
pkg_path = subpkg_path
|
pkg_path = subpkg_path
|
||||||
|
|
||||||
|
|
@ -724,18 +748,25 @@ def get_console_log(
|
||||||
# - 'sub:info,x:cancel' -> per-sub-logger levels; each `<name>` is
|
# - 'sub:info,x:cancel' -> per-sub-logger levels; each `<name>` is
|
||||||
# RELATIVE to `pkg_name` (must NOT include
|
# RELATIVE to `pkg_name` (must NOT include
|
||||||
# the `pkg_name` token itself), eg.
|
# the `pkg_name` token itself), eg.
|
||||||
# 'devx:runtime,trionics:cancel'.
|
# 'devx.debug:runtime,trionics:cancel'.
|
||||||
#
|
#
|
||||||
# !GRANULARITY CAVEAT! sub-logger names are matched at the
|
# !GRANULARITY! sub-logger names match at the `pkg_name.<name>`
|
||||||
# `pkg_name.<name>` *logger* level, which (per `get_logger()`'s
|
# *logger* level — which (per `get_logger()`'s name-derivation) is
|
||||||
# name-derivation) is effectively the *sub-package* granularity:
|
# *sub-PACKAGE* granularity, addressable at ANY nesting depth:
|
||||||
# - leaf module-names are stripped, so 'devx.debug' collapses to
|
# - 'devx.debug' -> the `tractor.devx.debug` logger, DISTINCT from a
|
||||||
# the same `tractor.devx` logger as a bare 'devx' (you CANNOT
|
# bare 'devx' -> `tractor.devx` (its parent). Setting `devx` also
|
||||||
# isolate a single leaf-module's emits from its sub-pkg).
|
# 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
|
# - top-level lib modules (eg. `tractor.to_asyncio`) emit under the
|
||||||
# *root* `pkg_name` logger (their `__package__` IS `pkg_name`),
|
# *root* `pkg_name` logger (their `__package__` IS `pkg_name`), so
|
||||||
# so a 'to_asyncio:<level>' filter targets a phantom child that
|
# a 'to_asyncio:<level>' entry targets a phantom child that nothing
|
||||||
# nothing emits to -> no-op. Use the root-level form for those.
|
# emits to -> no-op. Use the bare-level/root form for those.
|
||||||
LogSpec = str|bool
|
LogSpec = str|bool
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue