Skip to content

Basic logging utilities and filtering#925

Draft
iamhatesz wants to merge 3 commits intoroboflow:developfrom
iamhatesz:pr/logger
Draft

Basic logging utilities and filtering#925
iamhatesz wants to merge 3 commits intoroboflow:developfrom
iamhatesz:pr/logger

Conversation

@iamhatesz
Copy link
Copy Markdown
Contributor

Description

This is a PR solving issues described in #835

In summary:

  • stick with the standard library: logging and warnings
  • setup_logger utility to initialize supervision loggers
  • TimeBetweenOccurrencesFilter to use when we need to control the frequency of logging events

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

How has this change been tested, please provide a testcase or example of how you tested the change?

There are unit tests testing this functionality.

Any specific deployment considerations

Once we agree on the final interface, I'll make changes to the docs.

Docs

  • Docs updated? What were the changes:

@onuralpszr onuralpszr self-requested a review February 28, 2024 13:23
@onuralpszr onuralpszr added the API:logger Supervision Logging label Feb 28, 2024
@Borda Borda requested a review from SkalskiP as a code owner January 7, 2026 17:13
@Borda Borda requested review from Copilot and removed request for onuralpszr January 11, 2026 22:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces basic logging utilities to the supervision library, implementing standard library logging and warnings support as described in issue #835. The changes include a logger setup utility and a time-based filter for controlling log frequency.

Changes:

  • Added setup_logger() utility function to initialize supervision loggers with console output
  • Implemented TimeBetweenOccurrencesFilter class to throttle repeated log messages based on time intervals
  • Added comprehensive unit tests for the new logging functionality

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.

File Description
supervision/utils/logging.py Implements core logging utilities including setup_logger function and TimeBetweenOccurrencesFilter class
test/utils/test_logging.py Provides unit tests for logger setup and time-based filtering functionality

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

from typing import Dict, Tuple


def setup_logger(**levels: int):
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setup_logger function is missing comprehensive documentation. Following the codebase patterns (e.g., functions in supervision/utils/file.py), this function should include a docstring with a description, Args section explaining the **levels parameter (what logger names are expected, what level values are valid), Returns section, and ideally an Examples section showing how to use it.

Suggested change
def setup_logger(**levels: int):
def setup_logger(**levels: int):
"""
Configure logging for the ``supervision`` package and optional named loggers.
This helper sets up a console handler with a standard formatter and attaches
it to the ``supervision`` root logger. By default, the ``supervision`` logger
is configured with level :data:`logging.WARNING`. Additional logger levels
can be configured via keyword arguments.
Args:
**levels: Mapping of logger names to logging levels.
Each keyword argument name should be the name of a logger as used
with :func:`logging.getLogger`, and each value must be a valid
logging level accepted by the standard :mod:`logging` module.
Typical values are the predefined constants
:data:`logging.DEBUG`, :data:`logging.INFO`,
:data:`logging.WARNING`, :data:`logging.ERROR`, or
:data:`logging.CRITICAL`, or their corresponding integer values.
For example, to change the level of the top-level ``"supervision"``
logger or one of its children such as ``"supervision.my_module"``,
pass::
setup_logger(supervision=logging.INFO,
supervision_my_module=logging.DEBUG)
In this case the keyword names correspond to logger names that will
be passed to :func:`logging.getLogger`. Ensure that the provided
names match the loggers used in your application (e.g.
``"supervision"`` or ``"supervision.my_module"``).
Returns:
None: This function configures global logging state and does not
return a value.
Examples:
Basic setup using default levels::
from supervision.utils.logging import setup_logger
setup_logger()
Configure specific logger levels::
import logging
from supervision.utils.logging import setup_logger
setup_logger(
supervision=logging.INFO,
supervision_detection=logging.DEBUG,
)
After calling :func:`setup_logger`, loggers obtained via
:func:`logging.getLogger` (for example, ``logging.getLogger("supervision")``)
will use the configured levels and the shared console handler.
"""

Copilot uses AI. Check for mistakes.


def function_that_logs(logger: logging.Logger, i: int):
logger.warning("Info message %d", i)
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming is misleading here. The log message says "Info message" but it's actually being logged at the WARNING level. Consider changing the message to "Warning message" or something more descriptive to avoid confusion in tests.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +46
def filter(self, record: logging.LogRecord) -> bool:
record_id = _get_record_id(record)
now = datetime.fromtimestamp(record.created)
last_activity = self._last_activity.get(record_id)
self._last_activity[record_id] = now
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _last_activity dictionary is accessed and modified without thread synchronization. In multi-threaded environments where the same logger with this filter is used across multiple threads, this could lead to race conditions. Consider using threading.Lock to protect access to this shared state, or document that this filter is not thread-safe.

Copilot uses AI. Check for mistakes.
captured = capsys.readouterr()
assert "Info message 1" not in captured.err

time.sleep(0.1)
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test uses time.sleep(0.1) to sleep for 100 milliseconds, which exactly matches the filter's timedelta(milliseconds=100). This creates a potential timing issue where the test could be flaky - if the sleep is slightly shorter than expected or if there's any overhead, the assertion may fail. Consider using a slightly longer sleep (e.g., 0.11 or 0.15 seconds) to make the test more robust.

Suggested change
time.sleep(0.1)
time.sleep(0.11)

Copilot uses AI. Check for mistakes.
super().__init__()
self._min_interval = min_interval

self._last_activity: Dict[_LogId, datetime] = {}
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _last_activity dictionary grows unbounded as it stores timestamps for every unique log location (pathname, lineno) encountered. In long-running applications with many different log calls, this could lead to memory accumulation. Consider implementing a cleanup mechanism (e.g., removing entries older than a certain threshold, or using a bounded cache like functools.lru_cache pattern) or documenting this behavior.

Copilot uses AI. Check for mistakes.
class TimeBetweenOccurrencesFilter(logging.Filter):
"""
Adds filtering based on time elapsed between two same logging calls.
Useful to prevent displaying too much messages, e.g. in the loop.
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring contains a grammatical error. It should be "too many messages" instead of "too much messages" since "messages" is a countable noun.

Suggested change
Useful to prevent displaying too much messages, e.g. in the loop.
Useful to prevent displaying too many messages, e.g. in the loop.

Copilot uses AI. Check for mistakes.

def test_logging_without_setup_logger(capsys: pytest.CaptureFixture[str]):
logger = logging.getLogger("supervision.test_logging_without_setup_logger")
logger.warning("Info message")
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming is misleading here. The log message says "Info message" but it's actually being logged at the WARNING level. Consider changing the message to "Warning message" or something more descriptive to avoid confusion in tests.

Copilot uses AI. Check for mistakes.
def test_setup_logger(capsys: pytest.CaptureFixture[str]):
setup_logger()
logger = logging.getLogger("supervision.test_setup_logger")
logger.warning("Info message")
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming is misleading here. The log message says "Info message" but it's actually being logged at the WARNING level. Consider changing the message to "Warning message" or something more descriptive to avoid confusion in tests.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +33
Adds filtering based on time elapsed between two same logging calls.
Useful to prevent displaying too much messages, e.g. in the loop.
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TimeBetweenOccurrencesFilter class documentation is incomplete. Following codebase patterns, the docstring should include an Args section describing the min_interval parameter, and ideally an Examples section demonstrating how to use this filter with a logger. The description should also clarify what "same logging calls" means (i.e., calls from the same file and line number).

Suggested change
Adds filtering based on time elapsed between two same logging calls.
Useful to prevent displaying too much messages, e.g. in the loop.
Filter that limits how often log records from the same call site are emitted.
Two log records are considered to come from the same call site if they have
the same file path and line number (i.e., the same values of
``record.pathname`` and ``record.lineno``). For each such call site, log
records are only allowed if at least ``min_interval`` has elapsed since the
last emitted record from that location.
This is useful to prevent flooding logs with repeated messages in tight
loops or frequently executed code paths.
Args:
min_interval: Minimum allowed time between two emitted log records from
the same file and line number, expressed as a ``datetime.timedelta``.
Examples:
Basic usage with a logger::
import logging
from datetime import timedelta
from supervision.utils.logging import TimeBetweenOccurrencesFilter
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.addFilter(TimeBetweenOccurrencesFilter(
min_interval=timedelta(seconds=5)
))
logger.addHandler(handler)
# Within a tight loop, repeated messages from the same line will be
# emitted at most once every 5 seconds.
for i in range(1000):
logger.info("Processing item")

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@Borda Borda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it used/initialized anywhere yet or it is just a tool? Anyway I believe a docs will be very welcoming...

@Borda Borda marked this pull request as draft April 25, 2026 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API:logger Supervision Logging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants