improved logging
This commit is contained in:
@@ -10,7 +10,6 @@ from .enums import (
|
||||
SweepScale,
|
||||
SyncMode,
|
||||
)
|
||||
from .logging_utils import DEFAULT_LOG_LEVEL, configure_logging
|
||||
from .exceptions import (
|
||||
CRCError,
|
||||
DeviceDisconnectedError,
|
||||
@@ -39,7 +38,6 @@ from .models import (
|
||||
|
||||
__all__ = [
|
||||
"CRCError",
|
||||
"DEFAULT_LOG_LEVEL",
|
||||
"DeviceConfigVariant",
|
||||
"DeviceDisconnectedError",
|
||||
"DeviceInfo",
|
||||
@@ -55,7 +53,6 @@ __all__ = [
|
||||
"PacketType",
|
||||
"ParseError",
|
||||
"ProtocolVersionMismatch",
|
||||
"configure_logging",
|
||||
"SParameter",
|
||||
"StreamHandle",
|
||||
"SweepKind",
|
||||
|
||||
@@ -44,7 +44,18 @@ class GeneratorController:
|
||||
timeout_s: float,
|
||||
poll_interval_s: float,
|
||||
) -> DeviceStatus:
|
||||
"""Wait until available lock flags report the generator is ready."""
|
||||
"""Poll device status until the source/LO lock flags report the generator
|
||||
is ready, then return that status.
|
||||
|
||||
Polls every ``poll_interval_s`` seconds up to ``timeout_s`` total. Raises
|
||||
``TimeoutError`` if the locks do not assert in time and ``RuntimeError`` if
|
||||
the connected hardware family exposes no lock telemetry.
|
||||
"""
|
||||
logger.info(
|
||||
"Waiting for generator lock (timeout=%.2fs, poll_interval=%.2fs)",
|
||||
timeout_s,
|
||||
poll_interval_s,
|
||||
)
|
||||
|
||||
deadline = time.monotonic() + timeout_s
|
||||
|
||||
@@ -53,13 +64,19 @@ class GeneratorController:
|
||||
lock_values = [value for value in (status.source_locked, status.lo_locked) if value is not None]
|
||||
if lock_values:
|
||||
if all(lock_values):
|
||||
logger.info("Generator locked (family=%s)", status.family.name)
|
||||
return status
|
||||
else:
|
||||
logger.error(
|
||||
"Generator lock telemetry unavailable for hardware family %s",
|
||||
status.family.name,
|
||||
)
|
||||
raise RuntimeError(
|
||||
f"Generator lock telemetry is unavailable for hardware family {status.family.name}"
|
||||
)
|
||||
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
logger.warning("Timed out waiting for generator lock after %.2fs", timeout_s)
|
||||
raise TimeoutError("Timed out waiting for LibreVNA generator lock")
|
||||
time.sleep(min(poll_interval_s, remaining))
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
"""Logging helpers for applications embedding ``librevna_driver``.
|
||||
|
||||
The library uses standard ``logging`` module loggers under the
|
||||
``librevna_driver`` namespace and never configures global logging implicitly.
|
||||
Use :func:`configure_logging` in scripts/services when you want a convenient
|
||||
default console setup.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
_LOGGER_NAMESPACE = "librevna_driver"
|
||||
_DEFAULT_FORMAT = (
|
||||
"%(asctime)s | %(levelname)-8s | %(name)s | %(message)s"
|
||||
)
|
||||
DEFAULT_LOG_LEVEL = "INFO"
|
||||
|
||||
|
||||
def configure_logging(
|
||||
level: int | str | None = None,
|
||||
*,
|
||||
fmt: str = _DEFAULT_FORMAT,
|
||||
datefmt: str | None = "%Y-%m-%d %H:%M:%S",
|
||||
) -> None:
|
||||
"""Configure package logger with one stream handler.
|
||||
|
||||
This helper affects only the ``librevna_driver`` logger tree and is safe to
|
||||
call repeatedly (previous handlers attached by this function are replaced).
|
||||
When ``level`` is ``None``, :data:`DEFAULT_LOG_LEVEL` is used.
|
||||
"""
|
||||
|
||||
effective_level = level if level is not None else DEFAULT_LOG_LEVEL
|
||||
|
||||
logger = logging.getLogger(_LOGGER_NAMESPACE)
|
||||
logger.handlers.clear()
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(logging.Formatter(fmt=fmt, datefmt=datefmt))
|
||||
logger.addHandler(handler)
|
||||
logger.setLevel(_parse_level(effective_level))
|
||||
logger.propagate = False
|
||||
|
||||
|
||||
def _parse_level(level: int | str) -> int:
|
||||
"""Parse numeric or textual log level into logging constant."""
|
||||
if isinstance(level, int):
|
||||
return level
|
||||
|
||||
normalized = level.strip().upper()
|
||||
if normalized in logging.getLevelNamesMapping():
|
||||
return logging.getLevelNamesMapping()[normalized]
|
||||
raise ValueError(f"Unknown logging level: {level!r}")
|
||||
@@ -251,13 +251,14 @@ class SweepResult:
|
||||
return self.trace(parameter).imag
|
||||
|
||||
def to_npz(self, path: str) -> None:
|
||||
"""Save result as NumPy `.npz` archive."""
|
||||
"""Save the axis and all traces to a NumPy `.npz` archive at ``path``."""
|
||||
data: dict[str, np.ndarray] = {self.x_label: self.x}
|
||||
data.update(self.traces)
|
||||
np.savez(path, **data)
|
||||
logger.debug("Saved SweepResult to NPZ: %s (traces=%d)", path, len(self.traces))
|
||||
|
||||
def to_csv(self, path: str) -> None:
|
||||
"""Save result as CSV with `<trace>_real`/`<trace>_imag` columns."""
|
||||
"""Save the axis and traces to CSV at ``path``, with `<trace>_real`/`<trace>_imag` columns."""
|
||||
columns: list[np.ndarray] = [self.x]
|
||||
headers: list[str] = [self.x_label]
|
||||
for name, values in sorted(self.traces.items()):
|
||||
@@ -267,6 +268,7 @@ class SweepResult:
|
||||
headers.append(f"{name}_imag")
|
||||
matrix = np.column_stack(columns)
|
||||
np.savetxt(path, matrix, delimiter=",", header=",".join(headers), comments="")
|
||||
logger.debug("Saved SweepResult to CSV: %s (traces=%d)", path, len(self.traces))
|
||||
|
||||
|
||||
PacketPayload = Any
|
||||
|
||||
Reference in New Issue
Block a user