Skip to content

logger

Full name: tenets.utils.logger

logger

Logging utilities for Tenets.

Provides a single entrypoint get_logger that configures Rich logging once and returns child loggers for modules.

Functions:

get_logger

Python
get_logger(
    name: Optional[str] = None, level: Optional[int] = None
) -> logging.Logger

Return a configured logger.

Environment variables
  • TENETS_LOG_LEVEL: DEBUG|INFO|WARNING|ERROR|CRITICAL
Source code in tenets/utils/logger.py
Python
def get_logger(name: Optional[str] = None, level: Optional[int] = None) -> logging.Logger:
    """Return a configured logger.

    Environment variables:
      - TENETS_LOG_LEVEL: DEBUG|INFO|WARNING|ERROR|CRITICAL
    """
    env_level = os.getenv("TENETS_LOG_LEVEL")
    default_level_name = env_level.upper() if env_level else "INFO"
    level_map = {
        "DEBUG": logging.DEBUG,
        "INFO": logging.INFO,
        "WARNING": logging.WARNING,
        "ERROR": logging.ERROR,
        "CRITICAL": logging.CRITICAL,
    }
    resolved_level = level if level is not None else level_map.get(default_level_name, logging.INFO)

    # Configure root with the resolved level (explicit level overrides env)
    _configure_root(resolved_level)

    logger_name = name or "tenets"
    logger = logging.getLogger(logger_name)
    logger.propagate = True

    # Apply level rules:
    # - If explicit level provided, set it for this logger
    # - If requesting the base 'tenets' logger (or name None), set its level
    # - If requesting a child under 'tenets.', let it inherit (don't set level)
    # - Otherwise (arbitrary logger names), set the resolved level
    if level is not None:
        logger.setLevel(level)
    elif logger_name == "tenets":
        logger.setLevel(resolved_level)
    elif logger_name.startswith("tenets."):
        # Inherit from parent 'tenets' logger / root, do not set explicit level
        pass
    else:
        logger.setLevel(resolved_level)

    return logger

configure_logging

Python
configure_logging(
    stderr_level: int = logging.WARNING,
    file_level: int = logging.INFO,
    log_file: Optional[str] = None,
) -> None

Route detailed logs to a file while keeping stderr quiet.

Used by the MCP server: stderr (which the MCP client surfaces as [ERROR]) receives only stderr_level and above, enforced by a filter that survives later level changes; log_file receives file_level and above for debugging. Replaces the previous basicConfig that leaked INFO onto stderr.

Source code in tenets/utils/logger.py
Python
def configure_logging(
    stderr_level: int = logging.WARNING,
    file_level: int = logging.INFO,
    log_file: Optional[str] = None,
) -> None:
    """Route detailed logs to a file while keeping stderr quiet.

    Used by the MCP server: stderr (which the MCP client surfaces as ``[ERROR]``)
    receives only ``stderr_level`` and above, enforced by a filter that survives
    later level changes; ``log_file`` receives ``file_level`` and above for
    debugging. Replaces the previous basicConfig that leaked INFO onto stderr.
    """
    global _CONFIGURED, _CURRENT_LEVEL

    root = logging.getLogger()
    for h in list(root.handlers):
        root.removeHandler(h)

    stderr_handler = logging.StreamHandler(sys.stderr)
    stderr_handler.setLevel(stderr_level)
    stderr_handler.addFilter(_MinLevelFilter(stderr_level))
    stderr_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
    root.addHandler(stderr_handler)

    if log_file:
        try:
            log_dir = os.path.dirname(log_file)
            if log_dir:
                os.makedirs(log_dir, exist_ok=True)
            file_handler = logging.FileHandler(log_file)
            file_handler.setLevel(file_level)
            file_handler.addFilter(_MinLevelFilter(file_level))
            file_handler.setFormatter(
                logging.Formatter(
                    "%(asctime)s %(levelname)-8s %(name)s:%(lineno)d %(message)s",
                    datefmt="%Y-%m-%d %H:%M:%S",
                )
            )
            root.addHandler(file_handler)
        except OSError:
            pass

    effective = min(stderr_level, file_level)
    root.setLevel(effective)
    _CONFIGURED = True
    _CURRENT_LEVEL = effective