"""Logging utilities for FastMCP."""
import contextlib
import logging
from typing import Any, Literal, cast
from rich.console import Console
from rich.logging import RichHandler
import fastmcp
def get_logger(name: str) -> logging.Logger:
"""Get a logger nested under FastMCP namespace.
Args:
name: the name of the logger, which will be prefixed with 'FastMCP.'
Returns:
a configured logger instance
"""
return logging.getLogger(f"fastmcp.{name}")
def configure_logging(
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | int = "INFO",
logger: logging.Logger | None = None,
enable_rich_tracebacks: bool | None = None,
**rich_kwargs: Any,
) -> None:
"""
Configure logging for FastMCP.
Args:
logger: the logger to configure
level: the log level to use
rich_kwargs: the parameters to use for creating RichHandler
"""
# Check if logging is disabled in settings
if not fastmcp.settings.log_enabled:
return
# Use settings default if not specified
if enable_rich_tracebacks is None:
enable_rich_tracebacks = fastmcp.settings.enable_rich_tracebacks
if logger is None:
logger = logging.getLogger("fastmcp")
# Only configure the FastMCP logger namespace
handler = RichHandler(
console=Console(stderr=True),
rich_tracebacks=enable_rich_tracebacks,
**rich_kwargs,
)
formatter = logging.Formatter("%(message)s")
handler.setFormatter(formatter)
logger.setLevel(level)
# Remove any existing handlers to avoid duplicates on reconfiguration
for hdlr in logger.handlers[:]:
logger.removeHandler(hdlr)
logger.addHandler(handler)
# Don't propagate to the root logger
logger.propagate = False
@contextlib.contextmanager
def temporary_log_level(
level: str | None,
logger: logging.Logger | None = None,
enable_rich_tracebacks: bool | None = None,
**rich_kwargs: Any,
):
"""Context manager to temporarily set log level and restore it afterwards.
Args:
level: The temporary log level to set (e.g., "DEBUG", "INFO")
logger: Optional logger to configure (defaults to FastMCP logger)
enable_rich_tracebacks: Whether to enable rich tracebacks
**rich_kwargs: Additional parameters for RichHandler
Usage:
with temporary_log_level("DEBUG"):
# Code that runs with DEBUG logging
pass
# Original log level is restored here
"""
if level:
# Get the original log level from settings
original_level = fastmcp.settings.log_level
# Configure with new level
# Cast to proper type for type checker
log_level_literal = cast(
Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
level.upper(),
)
configure_logging(
level=log_level_literal,
logger=logger,
enable_rich_tracebacks=enable_rich_tracebacks,
**rich_kwargs,
)
try:
yield
finally:
# Restore original configuration using configure_logging
# This will respect the log_enabled setting
configure_logging(
level=original_level,
logger=logger,
enable_rich_tracebacks=enable_rich_tracebacks,
**rich_kwargs,
)
else:
yield