From 88cc68edd3e5ae7da5f7c4c271982080f668b41b Mon Sep 17 00:00:00 2001 From: goodboy Date: Wed, 24 Jun 2026 18:03:28 -0400 Subject: [PATCH] Fix `add_log_level()` re-registration value drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bound `StackLevelAdapter.` emit method captured its level via a `_level: int = value` default-arg at bind time, so a later `add_log_level(name, )` re-registration left the method emitting at the *stale* original level. Read `CUSTOM_LEVELS[name_up]` at call time instead — the method binds once but always tracks the current registered value. Also, - correct the `add_log_level()` docstring's "Idempotent" note to describe the call-time value lookup (was the misleading "no-op-ish refresh (won't clobber an already-bound method)"). - sync stale `_reap.py` doc/comment markers `tractor[` -> `_subactor[` to match the actual `_proctitle._def_prefix` proc-title prefix (doc-only drift; the code markers already referenced `_proctitle._def_prefix`). Review: PR #467 (copilot-pull-request-reviewer) https://github.com/goodboy/tractor/pull/467#pullrequestreview-4562945515 (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-code --- tractor/_testing/_reap.py | 14 +++++++------- tractor/log.py | 21 +++++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) 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}'