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¶
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