Skip to content

Print each PyHealth log line once in Jupyter and Colab - #1285

Merged
jhnwu3 merged 1 commit into
sunlabuiuc:masterfrom
solarsys:fix/duplicate-log-lines
Oct 11, 2026
Merged

jhnwu3 merged 1 commit into
sunlabuiuc:masterfrom
solarsys:fix/duplicate-log-lines

Conversation

@solarsys

@solarsys solarsys commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Why

pyhealth/__init__.py gives the pyhealth logger its own stdout handler, and the logger also propagates to the root logger. Jupyter, Colab and logging.basicConfig() all give the root logger a handler, so in those settings every PyHealth message prints twice: dataset loading, task processing, the Trainer's epoch summaries. It makes notebooks noticeably harder to read (it showed up throughout the Colab runs for #1281).

What

  • Set propagate = False on the pyhealth logger, next to where PyHealth adds its handler, with a comment showing how to opt back in.
  • Why this fix and not "only add the handler when root has none": that check would run at import time, so a script that imports PyHealth and then calls logging.basicConfig would still print twice. Turning off propagation is deterministic and matches how most libraries that print by default behave.
  • Trade-off: an application that configures the root logger (say, to a file or a log collector) no longer receives PyHealth's messages there automatically. It can get them back with two lines, now documented:
    logging.getLogger("pyhealth").removeHandler(pyhealth.handler)
    logging.getLogger("pyhealth").propagate = True
  • Unchanged: the INFO level, the stdout handler, child loggers (pyhealth.trainer, pyhealth.datasets, ...) still reach PyHealth's handler, and Trainer's log.txt file logging is unaffected.
  • Docs: a "Controlling PyHealth's log output" entry in docs/faq.rst. Example: examples/logging_setup.py (no data needed) shows the default, a quieter level, and routing through root.

Once this is merged, the logging.getLogger("pyhealth").propagate = False line in the tutorials' install cell (#1281) can be dropped.

Tests

tests/core/test_logging_setup.py (synthetic, no data):

  • with a root handler attached, a message from a pyhealth.* logger is printed once by PyHealth's handler and not by the root handler (fails on master, passes here);
  • INFO is still printed and DEBUG is not;
  • removing the handler and re-enabling propagation routes messages through root;
  • Trainer's set_logger still writes log.txt, and the message prints once.

Also ran the modules that use assertLogs on PyHealth loggers and the Trainer-based tests (test_early_stopping, test_model_mode, test_xgboost_model, test_nested_processor_truncation, test_default_cache_dir): 45 tests OK. tools/check_pr_rules.py passes.

🤖 Generated with Claude Code

The pyhealth logger has its own stdout handler and also propagated to the
root logger. Jupyter, Colab and logging.basicConfig give the root logger a
handler, so every message printed twice. Stop propagation from the pyhealth
logger; users who want PyHealth messages routed through their own root
configuration can remove pyhealth.handler and turn propagation back on.

Adds tests, a FAQ entry and examples/logging_setup.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@solarsys
solarsys requested a review from jhnwu3 October 9, 2026 17:16

@jhnwu3 jhnwu3 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

@jhnwu3
jhnwu3 merged commit 6893b2a into sunlabuiuc:master Oct 11, 2026
2 checks passed
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