80 lines
2.4 KiB
Python
80 lines
2.4 KiB
Python
"""Logging helpers for Pather."""
|
|
from typing import TYPE_CHECKING, Any
|
|
from collections.abc import Iterator, Sequence
|
|
import logging
|
|
import numpy
|
|
from contextlib import contextmanager
|
|
|
|
if TYPE_CHECKING:
|
|
from .pather import Pather
|
|
|
|
|
|
def _format_log_args(**kwargs) -> str:
|
|
arg_strs = []
|
|
for k, v in kwargs.items():
|
|
if isinstance(v, str | int | float | bool | None):
|
|
arg_strs.append(f"{k}={v}")
|
|
elif isinstance(v, numpy.ndarray):
|
|
arg_strs.append(f"{k}={v.tolist()}")
|
|
elif isinstance(v, list | tuple) and len(v) <= 10:
|
|
arg_strs.append(f"{k}={v}")
|
|
else:
|
|
arg_strs.append(f"{k}=...")
|
|
return ", ".join(arg_strs)
|
|
|
|
|
|
class PatherLogger:
|
|
"""
|
|
Encapsulates state for Pather diagnostic logging.
|
|
"""
|
|
debug: bool
|
|
indent: int
|
|
depth: int
|
|
|
|
def __init__(self, debug: bool = False) -> None:
|
|
self.debug = debug
|
|
self.indent = 0
|
|
self.depth = 0
|
|
|
|
def _log(self, module_name: str, msg: str) -> None:
|
|
if self.debug and self.depth <= 1:
|
|
log_obj = logging.getLogger(module_name)
|
|
log_obj.info(' ' * self.indent + msg)
|
|
|
|
@contextmanager
|
|
def log_operation(
|
|
self,
|
|
pather: 'Pather',
|
|
op: str,
|
|
portspec: str | Sequence[str] | None = None,
|
|
**kwargs: Any,
|
|
) -> Iterator[None]:
|
|
if not self.debug or self.depth > 0:
|
|
self.depth += 1
|
|
try:
|
|
yield
|
|
finally:
|
|
self.depth -= 1
|
|
return
|
|
|
|
target = f"({portspec})" if portspec else ""
|
|
module_name = pather.__class__.__module__
|
|
self._log(module_name, f"Operation: {op}{target} {_format_log_args(**kwargs)}")
|
|
|
|
before_ports = {name: port.copy() for name, port in pather.ports.items()}
|
|
self.depth += 1
|
|
self.indent += 1
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
after_ports = pather.ports
|
|
for name in sorted(after_ports.keys()):
|
|
if name not in before_ports or after_ports[name] != before_ports[name]:
|
|
self._log(module_name, f"Port {name}: {pather.ports[name].describe()}")
|
|
for name in sorted(before_ports.keys()):
|
|
if name not in after_ports:
|
|
self._log(module_name, f"Port {name}: removed")
|
|
|
|
self.indent -= 1
|
|
self.depth -= 1
|