diff --git a/libraries/getitrack/pyproject.toml b/libraries/getitrack/pyproject.toml index 5ceb9cd75cf..06ad27a4ee7 100644 --- a/libraries/getitrack/pyproject.toml +++ b/libraries/getitrack/pyproject.toml @@ -21,7 +21,6 @@ classifiers = [ "Programming Language :: Python :: 3.14", ] dependencies = [ - "loguru>=0.7", "numpy>=2.0", "pydantic>=2.7", "pyyaml==6.0.3", diff --git a/libraries/getitrack/src/getitrack/__init__.py b/libraries/getitrack/src/getitrack/__init__.py index 0f71bef5b21..3c1d3712957 100644 --- a/libraries/getitrack/src/getitrack/__init__.py +++ b/libraries/getitrack/src/getitrack/__init__.py @@ -2,10 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 """getitrack: Multi-object tracking toolkit.""" -from loguru import logger - -# Stay silent until the application opts in via `logger.enable("getitrack")` -# or a tracker's `verbose` flag. -logger.disable("getitrack") - __version__ = "0.1.0" diff --git a/libraries/getitrack/src/getitrack/core/base.py b/libraries/getitrack/src/getitrack/core/base.py index 451b232214a..dde7091615f 100644 --- a/libraries/getitrack/src/getitrack/core/base.py +++ b/libraries/getitrack/src/getitrack/core/base.py @@ -92,7 +92,7 @@ def _log_update(self, detections: Detections, tracked: TrackedDetections) -> Non ) ) LOGGER.info( - "frame {:4}: {} detections, {} tracks [id:class:score {}]", + "frame %4d: %d detections, %d tracks [id:class:score %s]", detections.frame_id, len(detections), len(tracked), diff --git a/libraries/getitrack/src/getitrack/core/track.py b/libraries/getitrack/src/getitrack/core/track.py index 511aa4fd40d..e6d93ac244f 100644 --- a/libraries/getitrack/src/getitrack/core/track.py +++ b/libraries/getitrack/src/getitrack/core/track.py @@ -91,7 +91,7 @@ def mark_hit(self, bbox: np.ndarray, score: float, lifecycle: LifecycleConfig) - assert_never(self.state) if self.state != prev_state: LOGGER.debug( - "track {}: {} -> {} on hit (hits={})", self.track_id, prev_state.name, self.state.name, self.hits + "track %s: %s -> %s on hit (hits=%s)", self.track_id, prev_state.name, self.state.name, self.hits ) def mark_miss(self, lifecycle: LifecycleConfig) -> None: @@ -114,7 +114,7 @@ def mark_miss(self, lifecycle: LifecycleConfig) -> None: assert_never(self.state) if self.state != prev_state: LOGGER.debug( - "track {}: {} -> {} on miss (time_since_update={})", + "track %s: %s -> %s on miss (time_since_update=%s)", self.track_id, prev_state.name, self.state.name, diff --git a/libraries/getitrack/src/getitrack/logger.py b/libraries/getitrack/src/getitrack/logger.py index 2fceebf4ba7..5ce45e31593 100644 --- a/libraries/getitrack/src/getitrack/logger.py +++ b/libraries/getitrack/src/getitrack/logger.py @@ -1,19 +1,27 @@ # Copyright (C) 2026 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -"""Package-wide loguru logger. +"""Standard-library logger for the ``getitrack`` package. -Logging is disabled for ``getitrack`` by default (see -``getitrack/__init__.py``); the library stays silent until the application -calls ``logger.enable("getitrack")`` or a tracker runs with ``verbose``. +A ``NullHandler`` keeps the package silent until the application configures +logging or a tracker runs with ``verbose``. """ from __future__ import annotations -from loguru import logger +import logging -LOGGER = logger +LOGGER = logging.getLogger("getitrack") +LOGGER.addHandler(logging.NullHandler()) -def enable_logging() -> None: - """Lift the default suppression of getitrack log records.""" - logger.enable("getitrack") +def enable_logging(level: int = logging.INFO) -> None: + """Set the logger to ``level`` and attach a console handler if none exists. + + Idempotent, so repeated ``verbose`` construction adds at most one handler. + """ + LOGGER.setLevel(level) + if not any(isinstance(handler, logging.StreamHandler) for handler in LOGGER.handlers): + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(message)s")) + LOGGER.addHandler(handler) + LOGGER.propagate = False diff --git a/libraries/getitrack/tests/unit/test_base.py b/libraries/getitrack/tests/unit/test_base.py index 4db3d41cfaa..85a821afcd9 100644 --- a/libraries/getitrack/tests/unit/test_base.py +++ b/libraries/getitrack/tests/unit/test_base.py @@ -4,17 +4,18 @@ from __future__ import annotations +import logging from collections.abc import Iterator from pathlib import Path import numpy as np import pytest -from loguru import logger from getitrack.config import AlgorithmType, LifecycleConfig, TrackerConfig from getitrack.core.base import BaseTracker from getitrack.core.detection import Detections, TrackedDetections from getitrack.core.registry import ALGORITHM_REGISTRY, register_algorithm +from getitrack.logger import LOGGER class _DummyConfig(TrackerConfig): @@ -41,18 +42,18 @@ def _update_impl(self, detections: Detections) -> TrackedDetections: @pytest.fixture(autouse=True) def _clean_registry() -> Iterator[None]: - """Snapshot and restore the registry around each test. + """Restore the registry and reset the getitrack logger after each test. - Also restores loguru's default-disabled state for ``getitrack``, since - a ``verbose`` tracker enables it process-wide. + A ``verbose`` tracker mutates the process-wide logger, so its handlers and + level are reset to the silent default. """ snapshot = dict(ALGORITHM_REGISTRY) ALGORITHM_REGISTRY.clear() - logger.disable("getitrack") yield ALGORITHM_REGISTRY.clear() ALGORITHM_REGISTRY.update(snapshot) - logger.disable("getitrack") + LOGGER.handlers = [h for h in LOGGER.handlers if isinstance(h, logging.NullHandler)] + LOGGER.setLevel(logging.NOTSET) def _register_dummy(name: str = "bytetrack") -> type[BaseTracker]: @@ -203,42 +204,26 @@ def _dets(self) -> Detections: frame_id=4, ) - def _capture(self, level: str = "INFO") -> tuple[list[str], int]: - messages: list[str] = [] - sink_id = logger.add(messages.append, level=level, format="{message}") - return messages, sink_id - - def test_verbose_logs_frame_summary(self): + def test_verbose_logs_frame_summary(self, caplog: pytest.LogCaptureFixture): _register_dummy("bytetrack") - messages, sink_id = self._capture() - try: + with caplog.at_level(logging.INFO, logger="getitrack"): BaseTracker.from_config(_DummyConfig(verbose=True)).update(self._dets()) - finally: - logger.remove(sink_id) - assert len(messages) == 1 - msg = messages[0] + assert len(caplog.records) == 1 + msg = caplog.records[0].getMessage() assert "frame 4" in msg assert "2 detections" in msg assert "1:3:0.90, 2:8:0.30" in msg - def test_default_is_silent(self): + def test_default_is_silent(self, caplog: pytest.LogCaptureFixture): _register_dummy("bytetrack") - messages, sink_id = self._capture(level="DEBUG") - try: + with caplog.at_level(logging.DEBUG, logger="getitrack"): BaseTracker.from_config(_DummyConfig()).update(self._dets()) - finally: - logger.remove(sink_id) - assert not messages + assert not caplog.records - def test_verbose_enables_package_logging(self): + def test_verbose_enables_package_logging(self, caplog: pytest.LogCaptureFixture): _register_dummy("bytetrack") - messages, sink_id = self._capture() - try: - # A non-verbose tracker stays suppressed even with a sink attached. + with caplog.at_level(logging.INFO, logger="getitrack"): BaseTracker.from_config(_DummyConfig()).update(self._dets()) - assert not messages - # A verbose tracker lifts the package-level suppression. + assert not caplog.records BaseTracker.from_config(_DummyConfig(verbose=True)).update(self._dets()) - assert len(messages) == 1 - finally: - logger.remove(sink_id) + assert len(caplog.records) == 1 diff --git a/libraries/getitrack/uv.lock b/libraries/getitrack/uv.lock index ab7a39d9804..467b7f114a4 100644 --- a/libraries/getitrack/uv.lock +++ b/libraries/getitrack/uv.lock @@ -24,7 +24,6 @@ wheels = [ name = "getitrack" source = { editable = "." } dependencies = [ - { name = "loguru" }, { name = "numpy" }, { name = "pydantic" }, { name = "pyyaml" }, @@ -43,7 +42,6 @@ lint = [ [package.metadata] requires-dist = [ - { name = "loguru", specifier = ">=0.7" }, { name = "numpy", specifier = ">=2.0" }, { name = "pydantic", specifier = ">=2.7" }, { name = "pyyaml", specifier = "==6.0.3" }, @@ -69,19 +67,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] -[[package]] -name = "loguru" -version = "0.7.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "win32-setctime", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, -] - [[package]] name = "numpy" version = "2.4.6" @@ -458,12 +443,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] - -[[package]] -name = "win32-setctime" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, -]