Source code for merton.logging
"""Structured logging wiring for merton.
The library uses :mod:`structlog`. By default it emits at ``WARNING`` and writes
human-readable output to stderr when run interactively, JSON otherwise.
Applications can configure logging themselves; we never touch the stdlib root
logger except via the user-facing :func:`enable` helper.
"""
from __future__ import annotations
import logging
import sys
import structlog
from ._config import get_config
[docs]
def get_logger(name: str | None = None) -> structlog.stdlib.BoundLogger:
"""Return a structlog bound logger named ``name`` (or the module's name)."""
return structlog.get_logger(name) # type: ignore[no-any-return]
[docs]
def enable(level: str | int = "INFO") -> None:
"""Wire up a sensible default logging configuration.
Convenience for interactive use; libraries should not call this from their
own ``__init__`` modules.
"""
if isinstance(level, str):
level = level.upper()
logging.basicConfig(level=level, format="%(message)s", stream=sys.stderr)
_configure_structlog(level)
def _configure_structlog(level: str | int) -> None:
is_tty = sys.stderr.isatty()
renderer: structlog.types.Processor = (
structlog.dev.ConsoleRenderer() if is_tty else structlog.processors.JSONRenderer()
)
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso", utc=True),
structlog.processors.StackInfoRenderer(),
renderer,
],
wrapper_class=structlog.make_filtering_bound_logger(
logging.getLevelName(level) if isinstance(level, str) else level
),
cache_logger_on_first_use=True,
)
_configure_structlog(get_config().log_level)
__all__ = ["enable", "get_logger"]