Compare commits

...
Author SHA1 Message Date
MervinPraisonandClaude Opus 5.5 62fe3ea489 fix: keep configure_structured_logging working when no root handler exists
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-12 23:32:49 +00:00
MervinPraisonandClaude Opus 5.5 8494dbd07d fix: do not install a root log handler on import unless LOGLEVEL is set
Split from #6295 (refs #6294).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-12 22:09:01 +00:00
2 changed files with 49 additions and 2 deletions

No files matched your search

@@ -143,10 +143,16 @@ def configure_root_logger():
Uses standard StreamHandler by default for performance.
RichHandler is only used when LOGLEVEL=DEBUG for better debugging experience.
Does nothing if the host application already configured the root logger.
Does nothing if the host application already configured the root logger,
or when ``LOGLEVEL`` is not set explicitly.
"""
if _host_configured_logging():
return
if 'LOGLEVEL' not in os.environ:
# A library must not install root handlers by default: that turns the
# host's later logging.basicConfig() into a no-op. Without handlers,
# WARNING+ records still reach stderr via logging.lastResort.
return
loglevel = os.environ.get('LOGLEVEL', 'WARNING').upper()
@@ -238,8 +244,13 @@ def configure_structured_logging():
"""
if os.environ.get('PRAISONAI_STRUCTURED_LOGS', '').lower() == 'true':
structured_formatter = StructuredFormatter()
root = logging.getLogger()
# Explicit opt-in: install a root handler if none exists (the SDK no
# longer installs one on import unless LOGLEVEL is set).
if not root.handlers:
root.addHandler(logging.StreamHandler())
# Reconfigure the root logger handler so propagating loggers get JSON output
for handler in logging.getLogger().handlers:
for handler in root.handlers:
handler.setFormatter(structured_formatter)
# Also reconfigure any praisonaiagents loggers that have their own handlers
for logger_name in logging.Logger.manager.loggerDict:
@@ -0,0 +1,36 @@
"""Regression tests for issue #6294: no root log handler by default."""
import logging
def test_import_leaves_root_logging_to_host(monkeypatch):
from praisonaiagents import _logging
root = logging.getLogger()
monkeypatch.delenv("LOGLEVEL", raising=False)
monkeypatch.setattr(root, "handlers", [])
_logging.configure_root_logger()
assert root.handlers == []
def test_explicit_loglevel_still_installs_root_handler(monkeypatch):
from praisonaiagents import _logging
root = logging.getLogger()
monkeypatch.setenv("LOGLEVEL", "WARNING")
monkeypatch.setattr(root, "handlers", [])
monkeypatch.setattr(root, "level", root.level)
_logging.configure_root_logger()
assert root.handlers
def test_structured_logging_works_without_loglevel(monkeypatch):
from praisonaiagents import _logging
root = logging.getLogger()
monkeypatch.delenv("LOGLEVEL", raising=False)
monkeypatch.setenv("PRAISONAI_STRUCTURED_LOGS", "true")
monkeypatch.setattr(root, "handlers", [])
_logging.configure_root_logger()
_logging.configure_structured_logging()
assert len(root.handlers) == 1
assert isinstance(root.handlers[0].formatter, _logging.StructuredFormatter)