Skip to content

Scope the log level set by verbose to the call - #175

Merged
cbrnr merged 4 commits into
xdf-modules:mainfrom
sappelhoff:fix-verbose-log-level
Aug 8, 2026
Merged

cbrnr merged 4 commits into
xdf-modules:mainfrom
sappelhoff:fix-verbose-log-level

Conversation

@sappelhoff

Copy link
Copy Markdown
Contributor

load_xdf(verbose=...) sets the level on the module logger and never restores it:

if verbose is not None:
    logger.setLevel(logging.DEBUG if verbose else logging.WARNING)

So one call reconfigures logging for the rest of the process. Two consequences:

  1. It overrides the application. A program that had configured pyxdf to its liking
    loses that configuration to any library that happens to call load_xdf(verbose=False)
    silently, and from code the application does not own.
  2. It breaks the documented meaning of verbose=None. The docstring says None "will
    use root logger level", which holds only while the logger's own level is NOTSET. After
    any earlier call that passed verbose, the logger has a level of its own and no longer
    inherits, so every later verbose=None call silently keeps the old setting.

The fix restores the level when the call returns, via a small decorator so the body of
load_xdf is untouched and its signature is preserved through functools.wraps:

@_scoped_log_level
def load_xdf(...):

verbose keeps working exactly as documented during the call; it just no longer leaks out
of it. Restoration happens on the error path too, which is where a leak is most likely to go
unnoticed.

load_xdf is the only function that needs this: it is the only place in the package that
calls setLevel. resolve_streams and match_streaminfos take no verbose and never
change the level, so they log at whatever level is in effect, which is the correct behaviour
for a library.

Tests

test/test_logging.py, 7 cases, no data files needed — they use a nonexistent path so
load_xdf raises after the level has been set:

  • the level is unchanged after the call for verbose in (None, True, False)
  • a level the application set beforehand survives a verbose=True call
  • verbose still decides whether the "Importing XDF file" record is emitted, so the fix
    cannot be mistaken for deleting the feature

Against the current release these fail as expected — assert 10 == 40, the logger left at
DEBUG where the application had set ERROR.

pytest test/: 366 passed, 13 skipped, 1 xfailed. ruff check and ruff format --check
clean.

Note

Deliberately left alone: load_xdf sets the level on pyxdf.pyxdf rather than on the
pyxdf parent, so while a call is in flight it still overrides a level set on the parent
namespace. Now that the override is scoped to the call, that is a much smaller wart, and
changing which logger is addressed would be a behaviour change rather than a fix. Happy to
follow up separately if you would rather it addressed the package logger.

`load_xdf(verbose=...)` set the level on the module logger and never restored
it, so a single call reconfigured logging for the rest of the process. It also
broke the documented meaning of `verbose=None` ("use root logger level") for
every later call: once the logger has a level of its own, it no longer inherits.

An application that had configured `pyxdf` to its liking would silently lose
that configuration to any library that happened to pass `verbose`.
@cbrnr

cbrnr commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Why a decorator though?

@sappelhoff

Copy link
Copy Markdown
Contributor Author

Why a decorator though?

What did you have in mind?

Wrapping it into a context manager or a try/finally would lead to a lot of indentations.

And resetting "manually" before each return seems a bit more unclean 🤔

@cbrnr

cbrnr commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Yes, a decorator is cleanest here. I've tweaked it a little bit though:

  1. It now contains the setting part as well.
  2. I've renamed it to verbose (like in MNE-Python)
  3. verbose=None is a no-op.

However, all of this wouldn't be necessary if we removed verbose from the function parameter list. I don't think this function should change global logging state in the first place; instead, a user can set the desired log level outside the function call. So ideally I'd want to remove the parameter, but we can merge this PR as a quick improvement.

@cbrnr
cbrnr merged commit a5ae971 into xdf-modules:main Aug 8, 2026
6 checks passed
@cbrnr

cbrnr commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Thanks @sappelhoff!

@sappelhoff
sappelhoff deleted the fix-verbose-log-level branch August 8, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants