diff --git a/tractor/_testing/_reap.py b/tractor/_testing/_reap.py index fa4df944..e9511e96 100644 --- a/tractor/_testing/_reap.py +++ b/tractor/_testing/_reap.py @@ -216,18 +216,18 @@ def _read_comm(pid: int) -> str: # regardless of cwd / venv path / launch context. Used by # `_is_tractor_subactor()` below. # -# - cmdline `tractor[`: matches the `setproctitle`-set form -# (`tractor[]`) — set in -# `_actor_child_main` for ALL backends, mutates argv via -# libc so visible in `/proc//cmdline`. +# - cmdline `_subactor[` (`_proctitle._def_prefix`): matches +# the `setproctitle`-set form (`_subactor[]`) +# — set in `_actor_child_main` for ALL backends, mutates +# argv via libc so visible in `/proc//cmdline`. # - cmdline `tractor._child`: matches the legacy # `python -m tractor._child --uid (...)` form. Catches # procs that died before `_actor_child_main` got to call # `setproctitle()` — argv from exec is still kernel- # visible at that point. -# - comm `tractor[`: same proctitle-set form, but visible +# - comm `_subactor[`: same proctitle-set form, but visible # via `/proc//comm` (kernel-truncated to ~15 bytes, -# `tractor[doggy:`). Critical for ZOMBIES — kernel +# `_subactor[doggy:`). Critical for ZOMBIES — kernel # preserves `comm` past task-exit until parent reaps, # while `cmdline` for zombies often reads as empty. _TRACTOR_PROC_CMDLINE_MARKERS: tuple[str, ...] = ( @@ -253,7 +253,7 @@ def _is_tractor_subactor(pid: int) -> bool: ''' # 1. cmdline match — catches both `setproctitle`-set - # `tractor[]` (live) AND legacy `python -m + # `_subactor[]` (live) AND legacy `python -m # tractor._child` (any) form. cmdline: str = _read_cmdline(pid) if any(m in cmdline for m in _TRACTOR_PROC_CMDLINE_MARKERS): diff --git a/tractor/log.py b/tractor/log.py index 2c6e6b71..6c8c9bc8 100644 --- a/tractor/log.py +++ b/tractor/log.py @@ -281,8 +281,11 @@ def add_log_level( `StackLevelAdapter` so `log.('msg')` works (and so `get_logger()`'s per-level method audit passes). - Idempotent: re-registering an existing name is a no-op-ish - refresh (won't clobber an already-bound method). + Idempotent: re-registering an existing name refreshes its + value + color in place. The bound `StackLevelAdapter.` + method reads the current `CUSTOM_LEVELS` value at call time, so + it tracks the new level across re-registration (the method + object is bound once, never re-bound/clobbered). ''' name_up: str = name.upper() @@ -294,16 +297,18 @@ def add_log_level( BOLD_PALETTE['bold'][name_up] = f'bold_{color}' if not hasattr(StackLevelAdapter, name_lo): - # bind via default-arg so `value` is captured (not - # late-bound); delegates to `.log()` exactly like the - # hand-written level methods above. + # read the level from `CUSTOM_LEVELS[name_up]` at CALL time + # (NOT captured at bind time) so a later + # `add_log_level(name, )` re-registration stays + # consistent with this once-bound method. The closure binds + # *this* call's `name_up` local (stable, never reassigned), + # so there's no late-binding hazard. Delegates to `.log()` + # exactly like the hand-written level methods above. def _emit( self, msg: str, - *, - _level: int = value, ) -> None: - return self.log(_level, msg) + return self.log(CUSTOM_LEVELS[name_up], msg) _emit.__name__ = name_lo _emit.__qualname__ = f'StackLevelAdapter.{name_lo}'