Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion libraries/getitrack/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ classifiers = [
"Programming Language :: Python :: 3.14",
]
dependencies = [
"loguru>=0.7",
"numpy>=2.0",
"pydantic>=2.7",
"pyyaml==6.0.3",
Expand Down
6 changes: 0 additions & 6 deletions libraries/getitrack/src/getitrack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion libraries/getitrack/src/getitrack/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions libraries/getitrack/src/getitrack/core/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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,
Expand Down
25 changes: 16 additions & 9 deletions libraries/getitrack/src/getitrack/logger.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
# 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)
Comment thread
omkar-334 marked this conversation as resolved.
51 changes: 18 additions & 33 deletions libraries/getitrack/tests/unit/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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]:
Expand Down Expand Up @@ -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
24 changes: 0 additions & 24 deletions libraries/getitrack/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.