Compare commits

..

4 Commits

Author SHA1 Message Date
Tyler Goodlet 2e0ba81f98 Use new `pkg_name` in log-sys test suites 2026-01-23 20:46:07 -05:00
Tyler Goodlet 7ccd7aa227 Implicitly name sub-logs by caller's mod
That is when no `name` is passed to `get_logger()`, try to introspect
the caller's `module.__name__` and use it to infer/get the "namespace
path" to that module the same as if using `name=__name__` as in the most
common usage.

Further, change the `_root_name` to be `pkg_name: str`, a public and
more obvious param name, and deprecate the former. This obviously adds
the necessary impl to make the new
`test_sys_log::test_implicit_mod_name_applied_for_child` test pass.

Impl detalles for `get_logger()`,
- add `pkg_name` and deprecate `_root_name`, include failover logic
  and a warning.
- implement calling module introspection using
  `inspect.stack()/getmodule()` to get both the `.__name__` and
  `.__package__` info alongside adjusted logic to set the `name`
  when not provided but only when a new `mk_sublog: bool` is set.
- tweak the `name` processing for implicitly set case,
  - rename `sub_name` -> `pkg_path: str` which is the path
    to the calling module minus that module's name component.
  - only partition `name` if `pkg_name` is `in` it.
  - use the `_root_log` for `pkg_name` duplication warnings.

Other/related,
- add types to various public mod vars missing them.
- rename `.log.log` -> `.log._root_log`.
2026-01-23 20:46:07 -05:00
Tyler Goodlet 6d16c6347f Add an implicit-pkg-path-as-logger-name test
A bit of test driven dev to anticipate support  of `.log.get_logger()`
usage such that it can be called from arbitrary sub-modules, themselves
embedded in arbitrary sub-pkgs, of some project; the when not provided,
the `sub_name` passed to the `Logger.getChild(<sub_name>)` will be set
as the sub-pkg path "down to" the calling module.

IOW if you call something like,

`log = tractor.log.get_logger(pkg_name='mypylib')`

from some `submod.py` in a project-dir that looks like,

mypylib/
  mod.py
  subpkg/
    submod.py  <- calling module

the `log: StackLevelAdapter` child-`Logger` instance will have a
`.name: str = 'mypylib.subpkg'`, discluding the `submod` part since this
already rendered as the `{filename}` header in `log.LOG_FORMAT`.

Previously similar behaviour would be obtained by passing
`get_logger(name=__name__)` in the calling module and so much so it
motivated me to make this the default, presuming we can introspect for
the info.

Impl deats,
- duplicated a `load_module_from_path()` from `modden` to load the
  `testdir` rendered py project dir from its path.
 |_should prolly factor it down to this lib anyway bc we're going to
   need it for hot code reload? (well that and `watchfiles` Bp)
- in each of `mod.py` and `submod.py` render the `get_logger()` code
  sin `name`, expecting the (coming shortly) implicit introspection
  feat to do this.
- do `.name` and `.parent` checks against expected sub-logger values
  from `StackLevelAdapter.logger.getChildren()`.
2026-01-23 20:46:07 -05:00
Tyler Goodlet f8b24082b9 Start a logging-sys unit-test module
To start ensuring that when `name=__name__` is passed we try to
de-duplicate the `_root_name` and any `leaf_mod: str` since it's already
included in the headers as `{filename}`.

Deats,
- heavily document the de-duplication `str.partition()`s in
  `.log.get_logger()` and provide the end fix by changing the predicate,
  `if rname == 'tractor':` -> `if rname == _root_name`.
  * also toss in some warnings for when we still detect duplicates.
- add todo comments around logging "filters" (vs. our "adapter").
- create the new `test_log_sys.test_root_pkg_not_duplicated()` which
  runs green with the fixes from ^.
- add a ton of test-suite todos both for existing and anticipated
  logging sys feats in the new mod.
2026-01-23 20:46:07 -05:00
35 changed files with 150 additions and 387 deletions

View File

@ -2,7 +2,7 @@
# https://pyproject-nix.github.io/pyproject.nix/templates.html#impure
# https://github.com/pyproject-nix/pyproject.nix/blob/master/templates/impure/flake.nix
{
description = "An impure overlay (w dev-shell) using `uv`";
description = "An impure overlay using `uv` with Nix(OS)";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
@ -29,17 +29,17 @@
{
default = pkgs.mkShell {
packages = [
packages = with pkgs; [
# XXX, ensure sh completions activate!
pkgs.bashInteractive
pkgs.bash-completion
bashInteractive
bash-completion
# XXX, on nix(os), use pkgs version to avoid
# build/sys-sh-integration issues
pkgs.ruff
# on nixos, use pkg(s)
ruff
pypkgs.ruff
pkgs.uv
pkgs.${cpython}# ?TODO^ how to set from `cpython` above?
uv
python313 # ?TODO^ how to set from `cpython` above?
];
shellHook = ''
@ -56,7 +56,7 @@
# - always use the ./py313/ venv-subdir
# - sync env with all extras
export UV_PROJECT_ENVIRONMENT=${venv_dir}
uv sync --dev --all-extras
uv sync --dev --all-extras --no-group lint
# ------ TIPS ------
# NOTE, to launch the py-venv installed `xonsh` (like @goodboy)

View File

@ -65,11 +65,7 @@ def loglevel(request):
import tractor
orig = tractor.log._default_loglevel
level = tractor.log._default_loglevel = request.config.option.loglevel
log = tractor.log.get_console_log(
level=level,
name='tractor', # <- enable root logger
)
log.info(f'Test-harness logging level: {level}\n')
tractor.log.get_console_log(level)
yield level
tractor.log._default_loglevel = orig

View File

@ -34,10 +34,7 @@ if TYPE_CHECKING:
# a fn that sub-instantiates a `pexpect.spawn()`
# and returns it.
type PexpectSpawner = Callable[
[str],
pty_spawn.spawn,
]
type PexpectSpawner = Callable[[str], pty_spawn.spawn]
@pytest.fixture
@ -112,11 +109,7 @@ def ctlc(
'https://github.com/goodboy/tractor/issues/320'
)
if (
mark.name == 'ctlcs_bish'
and
use_ctlc
):
if mark.name == 'ctlcs_bish':
pytest.skip(
f'Test {node} prolly uses something from the stdlib (namely `asyncio`..)\n'
f'The test and/or underlying example script can *sometimes* run fine '

View File

@ -2,15 +2,14 @@
`tractor.log`-wrapping unit tests.
'''
import importlib
from pathlib import Path
import shutil
import sys
from types import ModuleType
import pytest
import tractor
from tractor import (
_code_load,
log,
)
def test_root_pkg_not_duplicated_in_logger_name():
@ -23,15 +22,12 @@ def test_root_pkg_not_duplicated_in_logger_name():
project_name: str = 'pylib'
pkg_path: str = 'pylib.subpkg.mod'
assert not tractor.current_actor(
err_on_no_runtime=False,
)
proj_log = log.get_logger(
proj_log = tractor.log.get_logger(
pkg_name=project_name,
mk_sublog=False,
)
sublog = log.get_logger(
sublog = tractor.log.get_logger(
pkg_name=project_name,
name=pkg_path,
)
@ -41,6 +37,31 @@ def test_root_pkg_not_duplicated_in_logger_name():
assert 'mod' not in sublog.name
# ?TODO, move this into internal libs?
# -[ ] we already use it in `modden.config._pymod` as well
def load_module_from_path(
path: Path,
module_name: str|None = None,
) -> ModuleType:
'''
Taken from SO,
https://stackoverflow.com/a/67208147
which is based on stdlib docs,
https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
'''
module_name = module_name or path.stem
spec = importlib.util.spec_from_file_location(
module_name,
str(path),
)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
def test_implicit_mod_name_applied_for_child(
testdir: pytest.Pytester,
loglevel: str,
@ -57,7 +78,6 @@ def test_implicit_mod_name_applied_for_child(
mod_code: str = (
f'import tractor\n'
f'\n'
# f'breakpoint()\n' # if you want to trace it all
f'log = tractor.log.get_logger(pkg_name="{proj_name}")\n'
)
@ -85,30 +105,21 @@ def test_implicit_mod_name_applied_for_child(
pkg_subpkg_submod,
)
testdir.chdir()
# NOTE, to introspect the py-file-module-layout use (in .xsh
# syntax): `ranger @str(testdir)`
# XXX NOTE, once the "top level" pkg mod has been
# imported, we can then use `import` syntax to
# import it's sub-pkgs and modules.
pkgmod = _code_load.load_module_from_path(
pkgmod = load_module_from_path(
Path(pkg / '__init__.py'),
module_name=proj_name,
)
pkg_root_log = log.get_logger(
pkg_root_log = tractor.log.get_logger(
pkg_name=proj_name,
mk_sublog=False,
)
# the top level pkg-mod, created just now,
# by above API call.
assert pkg_root_log.name == proj_name
assert not pkg_root_log.logger.getChildren()
#
# ^TODO! test this same output but created via a `get_logger()`
# call in the `snakelib.__init__py`!!
# a first-pkg-level module should only
# use
from snakelib import mod
assert mod.log.name == proj_name

View File

@ -17,8 +17,9 @@ from tractor.log import (
get_console_log,
get_logger,
)
log = get_logger(__name__)
log = get_logger()
_resource: int = 0

View File

@ -37,7 +37,7 @@ from .ipc._uds import UDSAddress
if TYPE_CHECKING:
from ._runtime import Actor
log = get_logger()
log = get_logger(__name__)
# TODO, maybe breakout the netns key to a struct?
@ -259,8 +259,6 @@ def wrap_address(
case _:
# import pdbp; pdbp.set_trace()
# from tractor.devx import mk_pdb
# mk_pdb().set_trace()
raise TypeError(
f'Can not wrap unwrapped-address ??\n'
f'type(addr): {type(addr)!r}\n'

View File

@ -1,48 +0,0 @@
# tractor: structured concurrent "actors".
# Copyright 2018-eternity Tyler Goodlet.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
(Hot) coad (re-)load utils for python.
'''
import importlib
from pathlib import Path
import sys
from types import ModuleType
# ?TODO, move this into internal libs?
# -[ ] we already use it in `modden.config._pymod` as well
def load_module_from_path(
path: Path,
module_name: str|None = None,
) -> ModuleType:
'''
Taken from SO,
https://stackoverflow.com/a/67208147
which is based on stdlib docs,
https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
'''
module_name = module_name or path.stem
spec = importlib.util.spec_from_file_location(
module_name,
str(path),
)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module

View File

@ -113,7 +113,7 @@ if TYPE_CHECKING:
CallerInfo,
)
log = get_logger()
log = get_logger(__name__)
class Unresolved:
@ -2391,18 +2391,16 @@ async def open_context_from_portal(
case trio.Cancelled():
logmeth = log.cancel
cause: str = 'cancelled'
msg: str = (
f'ctx {ctx.side!r}-side {cause!r} with,\n'
f'{ctx.repr_outcome()!r}\n'
)
# XXX explicitly report on any non-graceful-taskc cases
case _:
cause: str = 'errored'
logmeth = log.exception
msg: str = f'ctx {ctx.side!r}-side {cause!r} with,\n'
logmeth(msg)
logmeth(
f'ctx {ctx.side!r}-side {cause!r} with,\n'
f'{ctx.repr_outcome()!r}\n'
)
if debug_mode():
# async with debug.acquire_debug_lock(portal.actor.uid):

View File

@ -53,7 +53,7 @@ if TYPE_CHECKING:
from ._runtime import Actor
log = get_logger()
log = get_logger(__name__)
@acm

View File

@ -50,7 +50,7 @@ if TYPE_CHECKING:
from ._spawn import SpawnMethodKey
log = get_logger()
log = get_logger(__name__)
def _mp_main(
@ -72,15 +72,11 @@ def _mp_main(
spawn_ctx: mp.context.BaseContext = try_set_start_method(start_method)
assert spawn_ctx
# XXX, enable root log at level
if actor.loglevel is not None:
log.info(
f'Setting loglevel for {actor.uid} to {actor.loglevel!r}'
)
get_console_log(
level=actor.loglevel,
name='tractor',
f'Setting loglevel for {actor.uid} to {actor.loglevel}'
)
get_console_log(actor.loglevel)
# TODO: use scops headers like for `trio` below!
# (well after we libify it maybe..)
@ -130,12 +126,8 @@ def _trio_main(
parent_addr=parent_addr
)
# XXX, enable root log at level
if actor.loglevel is not None:
get_console_log(
level=actor.loglevel,
name='tractor',
)
get_console_log(actor.loglevel)
log.info(
f'Starting `trio` subactor from parent @ '
f'{parent_addr}\n'

View File

@ -69,7 +69,7 @@ from ._streaming import (
if TYPE_CHECKING:
from ._runtime import Actor
log = get_logger()
log = get_logger(__name__)
class Portal:

View File

@ -289,12 +289,10 @@ async def open_root_actor(
for uw_addr in uw_reg_addrs
]
loglevel: str = (
loglevel = (
loglevel
or
log._default_loglevel
)
loglevel: str = loglevel.upper()
or log._default_loglevel
).upper()
if (
debug_mode
@ -325,10 +323,7 @@ async def open_root_actor(
)
assert loglevel
_log = log.get_console_log(
level=loglevel,
name='tractor',
)
_log = log.get_console_log(loglevel)
assert _log
# TODO: factor this into `.devx._stackscope`!!

View File

@ -59,7 +59,7 @@ if TYPE_CHECKING:
from .ipc import Channel
log = get_logger()
log = get_logger(__name__)
# TODO: the list

View File

@ -62,7 +62,7 @@ if TYPE_CHECKING:
from .ipc import IPCServer
log = get_logger()
log = get_logger(__name__)
class ActorNursery:

View File

@ -49,7 +49,7 @@ from tractor.msg import (
import wrapt
log = get_logger()
log = get_logger(__name__)
# TODO: yeah, i don't love this and we should prolly just
# write a decorator that actually keeps a stupid ref to the func

View File

@ -51,7 +51,7 @@ from tractor import (
)
from tractor.devx import debug
log = logmod.get_logger()
log = logmod.get_logger(__name__)
if TYPE_CHECKING:

View File

@ -59,7 +59,7 @@ from ._sigint import (
_ctlc_ignore_header as _ctlc_ignore_header
)
log = get_logger()
log = get_logger(__name__)
# ----------------
# XXX PKG TODO XXX

View File

@ -84,7 +84,7 @@ _crash_msg: str = (
'Opening a pdb REPL in crashed actor'
)
log = get_logger()
log = get_logger(__package__)
class BoxedMaybeException(Struct):

View File

@ -47,7 +47,7 @@ if TYPE_CHECKING:
Actor,
)
log = get_logger()
log = get_logger(__name__)
_ctlc_ignore_header: str = (
'Ignoring SIGINT while debug REPL in use'

View File

@ -58,7 +58,7 @@ from ._sigint import (
_ctlc_ignore_header as _ctlc_ignore_header
)
log = get_logger()
log = get_logger(__package__)
async def maybe_wait_for_debugger(

View File

@ -93,7 +93,7 @@ if TYPE_CHECKING:
# from ._post_mortem import BoxedMaybeException
from ._repl import PdbREPL
log = get_logger()
log = get_logger(__package__)
_pause_msg: str = 'Opening a pdb REPL in paused actor'
_repl_fail_msg: str|None = (
@ -628,7 +628,7 @@ def _set_trace(
log.pdb(
f'{_pause_msg}\n'
f'>(\n'
f'|_{actor.aid.uid}\n'
f'|_{actor.uid}\n'
f' |_{task}\n' # @ {actor.uid}\n'
# f'|_{task}\n'
# ^-TODO-^ more compact pformating?

View File

@ -81,7 +81,7 @@ if TYPE_CHECKING:
BoxedMaybeException,
)
log = get_logger()
log = get_logger(__name__)
class LockStatus(

View File

@ -60,7 +60,7 @@ if TYPE_CHECKING:
from ._transport import MsgTransport
log = get_logger()
log = get_logger(__name__)
_is_windows = platform.system() == 'Windows'

View File

@ -72,7 +72,7 @@ if TYPE_CHECKING:
from .._supervise import ActorNursery
log = log.get_logger()
log = log.get_logger(__name__)
async def maybe_wait_on_canced_subs(

View File

@ -59,7 +59,7 @@ except ImportError:
pass
log = get_logger()
log = get_logger(__name__)
SharedMemory = disable_mantracker()

View File

@ -41,7 +41,7 @@ from tractor.ipc._transport import (
)
log = get_logger()
log = get_logger(__name__)
class TCPAddress(

View File

@ -56,7 +56,7 @@ from tractor.msg import (
if TYPE_CHECKING:
from tractor._addr import Address
log = get_logger()
log = get_logger(__name__)
# (codec, transport)

View File

@ -63,7 +63,7 @@ if TYPE_CHECKING:
from ._runtime import Actor
log = get_logger()
log = get_logger(__name__)
def unwrap_sockpath(
@ -166,10 +166,6 @@ class UDSAddress(
)
if actor:
sockname: str = '::'.join(actor.uid) + f'@{pid}'
# ?^TODO, for `multiaddr`'s parser we can't use the `::`
# above^, SO maybe a `.` or something else here?
# sockname: str = '.'.join(actor.uid) + f'@{pid}'
# -[ ] CURRETLY using `.` BREAKS TEST SUITE tho..
else:
prefix: str = '<unknown-actor>'
if is_root_process():

View File

@ -25,7 +25,6 @@ built on `tractor`.
'''
from collections.abc import Mapping
from functools import partial
from inspect import (
FrameInfo,
getmodule,
@ -42,14 +41,12 @@ from types import ModuleType
import warnings
import colorlog # type: ignore
# ?TODO, some other (modern) alt libs?
# import coloredlogs
# import colored_traceback.auto # ?TODO, need better config?
import trio
from ._state import current_actor
_proj_name: str = 'tractor'
_default_loglevel: str = 'ERROR'
# Super sexy formatting thanks to ``colorlog``.
@ -127,16 +124,6 @@ class StackLevelAdapter(LoggerAdapter):
A (software) stack oriented logger "adapter".
'''
@property
def level(self) -> str:
'''
The currently set `str` emit level (in lowercase).
'''
return logging.getLevelName(
self.getEffectiveLevel()
).lower()
def at_least_level(
self,
level: str,
@ -284,14 +271,9 @@ def pformat_task_uid(
return f'{task.name}[{tid_part}]'
_curr_actor_no_exc = partial(
current_actor,
err_on_no_runtime=False,
)
_conc_name_getters = {
'task': pformat_task_uid,
'actor': lambda: _curr_actor_no_exc(),
'actor': lambda: current_actor(),
'actor_name': lambda: current_actor().name,
'actor_uid': lambda: current_actor().uid[1][:6],
}
@ -323,13 +305,8 @@ class ActorContextInfo(Mapping):
return f'no {key} context'
_proj_name: str = 'tractor'
def get_logger(
name: str|None = None,
# ^NOTE, setting `name=_proj_name=='tractor'` enables the "root
# logger" for `tractor` itself.
pkg_name: str = _proj_name,
# XXX, deprecated, use ^
_root_name: str|None = None,
@ -342,7 +319,6 @@ def get_logger(
# |_https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema
subsys_spec: str|None = None,
mk_sublog: bool = True,
_strict_debug: bool = False,
) -> StackLevelAdapter:
'''
@ -372,169 +348,79 @@ def get_logger(
DeprecationWarning,
stacklevel=2,
)
pkg_name: str = _root_name or pkg_name
log: Logger
log = rlog = logger or logging.getLogger(pkg_name)
pkg_name: str = _root_name
def get_caller_mod(
frames_up:int = 2
):
'''
Attempt to get the module which called `tractor.get_logger()`.
'''
callstack: list[FrameInfo] = stack()
caller_fi: FrameInfo = callstack[frames_up]
caller_mod: ModuleType = getmodule(caller_fi.frame)
return caller_mod
# --- Auto--naming-CASE ---
# -------------------------
# Implicitly introspect the caller's module-name whenever `name`
# if left as the null default.
#
# When the `pkg_name` is `in` in the `mod.__name__` we presume
# this instance can be created as a sub-`StackLevelAdapter` and
# that the intention is to get free module-path tracing and
# that the intention is get free module-path tracing and
# filtering (well once we implement that) oriented around the
# py-module code hierarchy of the consuming project.
#
if (
mk_sublog
pkg_name != _proj_name
and
name is None
and
pkg_name
mk_sublog
):
if (caller_mod := get_caller_mod()):
# ?XXX how is this `caller_mod.__name__` defined?
# => well by how the mod is imported.. XD
callstack: list[FrameInfo] = stack()
caller_fi: FrameInfo = callstack[1]
caller_mod: ModuleType = getmodule(caller_fi.frame)
if caller_mod:
# ?how is this `mod.__name__` defined?
# -> well by how the mod is imported..
# |_https://stackoverflow.com/a/15883682
#
mod_name: str = caller_mod.__name__
mod_pkg: str = caller_mod.__package__
log.info(
f'Generating sub-logger name,\n'
f'{mod_pkg}.{mod_name}\n'
)
# if pkg_name in caller_mod.__package__:
# from tractor.devx.debug import mk_pdb
# mk_pdb().set_trace()
mod_ns_path: str = caller_mod.__name__
mod_pkg_ns_path: str = caller_mod.__package__
# if 'snakelib' in mod_pkg_ns_path:
# import pdbp
# breakpoint()
if (
mod_pkg_ns_path in mod_ns_path
or
pkg_name in mod_ns_path
):
# proper_mod_name = mod_ns_path.lstrip(
proper_mod_name = mod_pkg_ns_path.removeprefix(
f'{pkg_name}.'
)
name = proper_mod_name
elif (
pkg_name
# and
# pkg_name in mod_ns_path
# pkg_name in mod_name
):
name = mod_ns_path
if _strict_debug:
msg: str = (
f'@ {get_caller_mod()}\n'
f'Generating sub-logger name,\n'
f'{pkg_name}.{name}\n'
)
if _curr_actor_no_exc():
_root_log.debug(msg)
elif pkg_name != _proj_name:
print(
f'=> tractor.log.get_logger():\n'
f'{msg}\n'
)
# build a root logger instance
log: Logger
rlog = log = (
logger
or
logging.getLogger(pkg_name)
)
name = mod_name
# XXX, lowlevel debuggin..
# if pkg_name != _proj_name:
# from tractor.devx.debug import mk_pdb
# mk_pdb().set_trace()
# NOTE: for handling for modules that use the unecessary,
# `get_logger(__name__)`
#
# we make the following stylistic choice:
# - always avoid duplicate project-package token
# in msg output: i.e. tractor.tractor.ipc._chan.py in header
# looks ridiculous XD
# - never show the leaf module name in the {name} part
# since in python the {filename} is always this same
# module-file.
if (
name
name != _proj_name
and
# ?TODO? more correct?
# _proj_name not in name
name != pkg_name
name
):
# ex. modden.runtime.progman
# -> rname='modden', _, pkg_path='runtime.progman'
if (
pkg_name
and
pkg_name in name
):
proper_name: str = name.removeprefix(
f'{pkg_name}.'
)
# if 'pylib' in name:
# import pdbp
# breakpoint()
msg: str = (
f'@ {get_caller_mod()}\n'
f'Duplicate pkg-name in sub-logger `name`-key?\n'
f'pkg_name = {pkg_name!r}\n'
f'name = {name!r}\n'
f'\n'
f'=> You should change your input params to,\n'
f'get_logger(\n'
f' pkg_name={pkg_name!r}\n'
f' name={proper_name!r}\n'
f')'
)
# assert _duplicate == rname
if _curr_actor_no_exc():
_root_log.warning(msg)
else:
print(
f'=> tractor.log.get_logger() ERROR:\n'
f'{msg}\n'
)
name = proper_name
# NOTE: for handling for modules that use `get_logger(__name__)`
# we make the following stylistic choice:
# - always avoid duplicate project-package token
# in msg output: i.e. tractor.tractor.ipc._chan.py in header
# looks ridiculous XD
# - never show the leaf module name in the {name} part
# since in python the {filename} is always this same
# module-file.
rname: str = pkg_name
pkg_path: str = name
# ex. modden.runtime.progman
# -> rname='modden', _, pkg_path='runtime.progman'
if pkg_name in name:
rname, _, pkg_path = name.partition('.')
# (
# rname,
# _,
# pkg_path,
# ) = name.partition('.')
# For ex. 'modden.runtime.progman'
# ex. modden.runtime.progman
# -> pkgpath='runtime', _, leaf_mod='progman'
(
subpkg_path,
_,
leaf_mod,
) = pkg_path.rpartition('.')
subpkg_path, _, leaf_mod = pkg_path.rpartition('.')
# NOTE: special usage for passing `name=__name__`,
#
@ -550,84 +436,36 @@ 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`.
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
# )
):
if rname == pkg_name:
pkg_path = subpkg_path
# XXX, do some double-checks for duplication of,
# - root-pkg-name, already in root logger
# - leaf-module-name already in `{filename}` header-field
if (
_strict_debug
and
pkg_name
and
pkg_name in pkg_path
):
if pkg_name in pkg_path:
_duplicate, _, pkg_path = pkg_path.partition('.')
if _duplicate:
msg: str = (
f'@ {get_caller_mod()}\n'
# assert _duplicate == rname
_root_log.warning(
f'Duplicate pkg-name in sub-logger key?\n'
f'pkg_name = {pkg_name!r}\n'
f'pkg_path = {pkg_path!r}\n'
)
# assert _duplicate == rname
if _curr_actor_no_exc():
_root_log.warning(msg)
else:
print(
f'=> tractor.log.get_logger() ERROR:\n'
f'{msg}\n'
)
# XXX, should never get here?
breakpoint()
if (
_strict_debug
and
leaf_mod
and
leaf_mod in pkg_path
):
msg: str = (
f'@ {get_caller_mod()}\n'
_root_log.warning(
f'Duplicate leaf-module-name in sub-logger key?\n'
f'leaf_mod = {leaf_mod!r}\n'
f'pkg_path = {pkg_path!r}\n'
)
if _curr_actor_no_exc():
_root_log.warning(msg)
else:
print(
f'=> tractor.log.get_logger() ERROR:\n'
f'{msg}\n'
)
# mk/get underlying (sub-)`Logger`
if (
not pkg_path
and
leaf_mod == pkg_name
):
# breakpoint()
if not pkg_path:
log = rlog
elif mk_sublog:
# breakpoint()
log = rlog.getChild(pkg_path)
log.level = rlog.level

View File

@ -68,7 +68,7 @@ from tractor.log import get_logger
if TYPE_CHECKING:
from tractor._context import Context
log = get_logger()
log = get_logger(__name__)
# TODO: unify with `MsgCodec` by making `._dec` part this?

View File

@ -77,7 +77,7 @@ if TYPE_CHECKING:
from tractor._streaming import MsgStream
log = get_logger()
log = get_logger(__name__)
_def_any_pldec: MsgDec[Any] = mk_dec(spec=Any)

View File

@ -51,7 +51,7 @@ from tractor.log import get_logger
# from tractor._addr import UnwrappedAddress
log = get_logger()
log = get_logger('tractor.msgspec')
# type variable for the boxed payload field `.pld`
PayloadT = TypeVar('PayloadT')
@ -202,10 +202,7 @@ class SpawnSpec(
# TODO: similar to the `Start` kwargs spec needed below, we need
# a hard `Struct` def for all of these fields!
_parent_main_data: dict
_runtime_vars: (
dict[str, Any]
#|RuntimeVars # !TODO
)
_runtime_vars: dict[str, Any]
# ^NOTE see `._state._runtime_vars: dict`
# module import capability

View File

@ -42,7 +42,7 @@ from trio.lowlevel import current_task
from msgspec import Struct
from tractor.log import get_logger
log = get_logger()
log = get_logger(__name__)
# TODO: use new type-vars syntax from 3.12
# https://realpython.com/python312-new-features/#dedicated-type-variable-syntax

View File

@ -49,7 +49,7 @@ if TYPE_CHECKING:
from tractor import ActorNursery
log = get_logger()
log = get_logger(__name__)
# A regular invariant generic type
T = TypeVar("T")

View File

@ -34,7 +34,7 @@ from typing import (
import trio
from tractor.log import get_logger
log = get_logger()
log = get_logger(__name__)
if TYPE_CHECKING:
@ -246,12 +246,23 @@ async def maybe_raise_from_masking_exc(
type(exc_match) # masked type
)
# Add to masked `exc_ctx`
if do_warn:
exc_ctx.add_note(note)
# don't unmask already known "special" cases..
if (
do_warn
and
type(exc_match) in always_warn_on
):
log.warning(note)
if (
do_warn
and
raise_unmasked
):
if len(masked) < 2:
# don't unmask already known "special" cases..
if (
_mask_cases
and
@ -272,26 +283,11 @@ async def maybe_raise_from_masking_exc(
)
raise exc_match
# ^?TODO, see above but, possibly unmasking sub-exc
# entries if there are > 1
# else:
# await pause(shield=True)
if type(exc_match) in always_warn_on:
import traceback
trace: list[str] = traceback.format_exception(
type(exc_ctx),
exc_ctx,
exc_ctx.__traceback__
)
tb_str: str = ''.join(trace)
log.warning(tb_str)
# XXX, for debug
# from tractor import pause
# await pause(shield=True)
if raise_unmasked:
raise exc_ctx from exc_match
raise exc_ctx from exc_match
# ??TODO, see above but, possibly unmasking sub-exc
# entries if there are > 1
# else:
# await pause(shield=True)
else:
raise