Fix `add_log_level()` re-registration value drift
The bound `StackLevelAdapter.<name>` emit method captured its level via a `_level: int = value` default-arg at bind time, so a later `add_log_level(name, <new-value>)` 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-codecustom_log_levels_api
parent
3727ce9e6f
commit
88cc68edd3
|
|
@ -216,18 +216,18 @@ def _read_comm(pid: int) -> str:
|
||||||
# regardless of cwd / venv path / launch context. Used by
|
# regardless of cwd / venv path / launch context. Used by
|
||||||
# `_is_tractor_subactor()` below.
|
# `_is_tractor_subactor()` below.
|
||||||
#
|
#
|
||||||
# - cmdline `tractor[`: matches the `setproctitle`-set form
|
# - cmdline `_subactor[` (`_proctitle._def_prefix`): matches
|
||||||
# (`tractor[<aid.reprol()>]`) — set in
|
# the `setproctitle`-set form (`_subactor[<aid.reprol()>]`)
|
||||||
# `_actor_child_main` for ALL backends, mutates argv via
|
# — set in `_actor_child_main` for ALL backends, mutates
|
||||||
# libc so visible in `/proc/<pid>/cmdline`.
|
# argv via libc so visible in `/proc/<pid>/cmdline`.
|
||||||
# - cmdline `tractor._child`: matches the legacy
|
# - cmdline `tractor._child`: matches the legacy
|
||||||
# `python -m tractor._child --uid (...)` form. Catches
|
# `python -m tractor._child --uid (...)` form. Catches
|
||||||
# procs that died before `_actor_child_main` got to call
|
# procs that died before `_actor_child_main` got to call
|
||||||
# `setproctitle()` — argv from exec is still kernel-
|
# `setproctitle()` — argv from exec is still kernel-
|
||||||
# visible at that point.
|
# visible at that point.
|
||||||
# - comm `tractor[`: same proctitle-set form, but visible
|
# - comm `_subactor[`: same proctitle-set form, but visible
|
||||||
# via `/proc/<pid>/comm` (kernel-truncated to ~15 bytes,
|
# via `/proc/<pid>/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,
|
# preserves `comm` past task-exit until parent reaps,
|
||||||
# while `cmdline` for zombies often reads as empty.
|
# while `cmdline` for zombies often reads as empty.
|
||||||
_TRACTOR_PROC_CMDLINE_MARKERS: tuple[str, ...] = (
|
_TRACTOR_PROC_CMDLINE_MARKERS: tuple[str, ...] = (
|
||||||
|
|
@ -253,7 +253,7 @@ def _is_tractor_subactor(pid: int) -> bool:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# 1. cmdline match — catches both `setproctitle`-set
|
# 1. cmdline match — catches both `setproctitle`-set
|
||||||
# `tractor[<aid>]` (live) AND legacy `python -m
|
# `_subactor[<aid>]` (live) AND legacy `python -m
|
||||||
# tractor._child` (any) form.
|
# tractor._child` (any) form.
|
||||||
cmdline: str = _read_cmdline(pid)
|
cmdline: str = _read_cmdline(pid)
|
||||||
if any(m in cmdline for m in _TRACTOR_PROC_CMDLINE_MARKERS):
|
if any(m in cmdline for m in _TRACTOR_PROC_CMDLINE_MARKERS):
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,11 @@ def add_log_level(
|
||||||
`StackLevelAdapter` so `log.<name>('msg')` works (and so
|
`StackLevelAdapter` so `log.<name>('msg')` works (and so
|
||||||
`get_logger()`'s per-level method audit passes).
|
`get_logger()`'s per-level method audit passes).
|
||||||
|
|
||||||
Idempotent: re-registering an existing name is a no-op-ish
|
Idempotent: re-registering an existing name refreshes its
|
||||||
refresh (won't clobber an already-bound method).
|
value + color in place. The bound `StackLevelAdapter.<name>`
|
||||||
|
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()
|
name_up: str = name.upper()
|
||||||
|
|
@ -294,16 +297,18 @@ def add_log_level(
|
||||||
BOLD_PALETTE['bold'][name_up] = f'bold_{color}'
|
BOLD_PALETTE['bold'][name_up] = f'bold_{color}'
|
||||||
|
|
||||||
if not hasattr(StackLevelAdapter, name_lo):
|
if not hasattr(StackLevelAdapter, name_lo):
|
||||||
# bind via default-arg so `value` is captured (not
|
# read the level from `CUSTOM_LEVELS[name_up]` at CALL time
|
||||||
# late-bound); delegates to `.log()` exactly like the
|
# (NOT captured at bind time) so a later
|
||||||
# hand-written level methods above.
|
# `add_log_level(name, <new-value>)` 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(
|
def _emit(
|
||||||
self,
|
self,
|
||||||
msg: str,
|
msg: str,
|
||||||
*,
|
|
||||||
_level: int = value,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
return self.log(_level, msg)
|
return self.log(CUSTOM_LEVELS[name_up], msg)
|
||||||
|
|
||||||
_emit.__name__ = name_lo
|
_emit.__name__ = name_lo
|
||||||
_emit.__qualname__ = f'StackLevelAdapter.{name_lo}'
|
_emit.__qualname__ = f'StackLevelAdapter.{name_lo}'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue