diff --git a/examples/weather.py b/examples/weather.py index 0a53a152d..9f749dc23 100644 --- a/examples/weather.py +++ b/examples/weather.py @@ -6,6 +6,7 @@ import os import httpx from pydantic import BaseModel, Field from fastmcp.server import FastMCP +from fastmcp.utilities.logging import configure_logging # Load env vars API_KEY = os.getenv("OPENWEATHER_API_KEY") @@ -117,13 +118,9 @@ app.add_dir_resource( def main(): import asyncio - import logging # Configure logging - logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - ) + configure_logging(level="INFO") # Run the server asyncio.run(FastMCP.run_stdio(app)) diff --git a/src/fastmcp/cli.py b/src/fastmcp/cli.py index b710e028e..fc431582d 100644 --- a/src/fastmcp/cli.py +++ b/src/fastmcp/cli.py @@ -1,15 +1,15 @@ """FastMCP CLI tools.""" import importlib.metadata -import logging import subprocess import sys from pathlib import Path import typer -# Configure logging -logger = logging.getLogger("mcp") +from .utilities.logging import get_logger + +logger = get_logger(__name__) app = typer.Typer( name="fastmcp", diff --git a/src/fastmcp/resources.py b/src/fastmcp/resources.py index d8e0ac2bf..2987cf8ea 100644 --- a/src/fastmcp/resources.py +++ b/src/fastmcp/resources.py @@ -3,7 +3,6 @@ import abc import asyncio import json -import logging from pathlib import Path from typing import Dict, Optional, Callable, Any from urllib.parse import parse_qs, urlparse @@ -11,7 +10,9 @@ from urllib.parse import parse_qs, urlparse import httpx from pydantic import BaseModel, field_validator -logger = logging.getLogger("mcp") +from .utilities.logging import get_logger + +logger = get_logger(__name__) class Resource(BaseModel): diff --git a/src/fastmcp/server.py b/src/fastmcp/server.py index 06e2bc7dd..8004a6d55 100644 --- a/src/fastmcp/server.py +++ b/src/fastmcp/server.py @@ -1,11 +1,8 @@ """FastMCP - A more ergonomic interface for MCP servers.""" -import asyncio import base64 import functools import json -import logging -from dataclasses import dataclass from typing import Any, Callable, Dict, Optional, Sequence, Union, Literal from mcp.server import Server as MCPServer @@ -19,7 +16,7 @@ from pydantic_settings import BaseSettings from .exceptions import ResourceError from .resources import Resource, FunctionResource, ResourceManager from .tools import ToolManager -from .utilities import get_logger, configure_logging +from .utilities.logging import get_logger, configure_logging logger = get_logger(__name__) diff --git a/src/fastmcp/tools.py b/src/fastmcp/tools.py index 4be52f95e..121fe95cb 100644 --- a/src/fastmcp/tools.py +++ b/src/fastmcp/tools.py @@ -6,9 +6,9 @@ from typing import Any, Callable, Dict, Optional from pydantic import BaseModel, Field, TypeAdapter from .exceptions import ToolError -import logging +from .utilities.logging import get_logger -logger = logging.getLogger("fastmcp") +logger = get_logger(__name__) class Tool(BaseModel): @@ -84,7 +84,7 @@ class ToolManager: existing = self._tools.get(tool.name) if existing: if self.warn_on_duplicate_tools: - logging.warning(f"Tool already exists: {tool.name}") + logger.warning(f"Tool already exists: {tool.name}") return existing self._tools[tool.name] = tool return tool diff --git a/src/fastmcp/utilities/__init__.py b/src/fastmcp/utilities/__init__.py index a943cff70..be448f97a 100644 --- a/src/fastmcp/utilities/__init__.py +++ b/src/fastmcp/utilities/__init__.py @@ -1,4 +1 @@ -"""Utility functions for FastMCP.""" -from .logging import get_logger, configure_logging - -__all__ = ["get_logger", "configure_logging"] +"""FastMCP utility modules.""" diff --git a/src/fastmcp/utilities/logging.py b/src/fastmcp/utilities/logging.py index 5a3656ccb..604b24e21 100644 --- a/src/fastmcp/utilities/logging.py +++ b/src/fastmcp/utilities/logging.py @@ -1,33 +1,30 @@ """Logging utilities for FastMCP.""" + import logging -from typing import Optional +from typing import Literal -def get_logger(name: Optional[str] = None) -> logging.Logger: - """Get a logger instance nested under the FastMCP namespace. - +def get_logger(name: str) -> logging.Logger: + """Get a logger nested under FastMCP namespace. + Args: - name: Optional name to append to the FastMCP namespace. - If provided, the logger will be named 'FastMCP.[name]'. - If not provided, returns the root FastMCP logger. - + name: The name of the logger, which will be prefixed with 'FastMCP.' + Returns: A configured logger instance """ - logger_name = "FastMCP" - if name: - logger_name = f"{logger_name}.{name}" - return logging.getLogger(logger_name) + return logging.getLogger(f"FastMCP.{name}") -def configure_logging(level: str = "INFO") -> None: - """Configure the root FastMCP logger. - +def configure_logging( + level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO", +) -> None: + """Configure logging for FastMCP. + Args: - level: The log level to use. Defaults to INFO. + level: The log level to use """ logging.basicConfig( - level=getattr(logging, level.upper()), + level=level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) - get_logger().setLevel(getattr(logging, level.upper()))