Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 3 additions & 2 deletions cmdstanpy/utils/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ def get_logger() -> logging.Logger:
"""cmdstanpy logger"""
logger = logging.getLogger("cmdstanpy")
if not logger.hasHandlers():
# send all messages to handlers
logger.setLevel(logging.DEBUG)
# Send all messages to handlers unless the user selected a level.
if logger.level == logging.NOTSET:
logger.setLevel(logging.DEBUG)
# add a default handler to the logger to WARNING and higher
handler = logging.StreamHandler()
handler.setLevel(logging.WARNING)
Expand Down
10 changes: 10 additions & 0 deletions docsrc/users-guide/outputs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ These functions also work as context managers for more local control:
fit = model.sample(data=data_file, show_progress=False)


An explicitly configured level is preserved when CmdStanPy initializes its
logger. For example, to show errors but suppress warnings, set the level before
using a model:

.. code-block:: python

import logging

logging.getLogger("cmdstanpy").setLevel(logging.ERROR)

For more fine-grained control, one can interact with the underlying ``logging``
library directly. For example, the following code installs a custom handler
that sends all logs (including the ``DEBUG`` logs, which are hidden by
Expand Down
33 changes: 33 additions & 0 deletions test/test_logging.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
"""Logging control tests"""

import logging
import subprocess
import sys

import pytest

import cmdstanpy


@pytest.mark.parametrize(
"level", [logging.NOTSET, logging.DEBUG, logging.WARNING, logging.ERROR]
)
def test_logger_initialization_preserves_level(level: int) -> None:
# A fresh process avoids pytest's logging handlers and the cached logger.
result = subprocess.run(
[
sys.executable,
"-c",
f"""
import logging
import cmdstanpy

logger = logging.getLogger('cmdstanpy')
logger.setLevel({level})
logger = cmdstanpy.utils.get_logger()
print(logger.level)
logger.warning('warning message')
logger.error('error message')
""",
],
capture_output=True,
text=True,
check=True,
)
expected_level = logging.DEBUG if level == logging.NOTSET else level
assert int(result.stdout) == expected_level
assert ('warning message' in result.stderr) == (level <= logging.WARNING)
assert 'error message' in result.stderr


def test_disable_logging(caplog: pytest.LogCaptureFixture) -> None:
logger = cmdstanpy.utils.logging.get_logger()

Expand Down
Loading