Skip to content

Commit 4a37062

Browse files
committed
fix: consolidate logging
1 parent dff2afc commit 4a37062

File tree

6 files changed

+132
-19
lines changed

6 files changed

+132
-19
lines changed

mcp_server/app.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
from mcp_server.settings import Config as cfg
88
from mcp_server.settings.logging import get_app_logger
99

10-
logger = get_app_logger()
10+
logger = get_app_logger("mcp_server.app")
1111

12-
# Configure authentication
1312
auth_provider = get_auth_provider()
1413

1514
mcp: FastMCP = FastMCP(

mcp_server/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from mcp_server.settings import Config as cfg
99
from mcp_server.settings.logging import get_app_logger
1010

11-
logger = get_app_logger()
11+
logger = get_app_logger("mcp_server.auth")
1212

1313

1414
def get_auth_provider() -> Optional[OAuthProvider]:

mcp_server/settings/config.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ class Config:
1818

1919
# Logging configuration
2020
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO").upper()
21-
LOG_FORMAT: str = os.getenv(
22-
"LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
23-
)
2421

2522
# JWT Authentication Settings
2623
JWT_PUBLIC_KEY: str = os.getenv("JWT_PUBLIC_KEY", "")

mcp_server/settings/logging.py

Lines changed: 127 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,140 @@
11
"""Logging configuration and utilities."""
22

33
import logging
4+
import re
5+
import sys
46

5-
from fastmcp.utilities.logging import get_logger
7+
from rich.console import Console
8+
from rich.logging import RichHandler
69

710
from .config import Config
811

912

10-
def get_app_logger() -> logging.Logger:
11-
"""
12-
Configure the FastMCP framework logger with application settings.
13+
class RichLoggingHandler(logging.StreamHandler):
14+
"""Custom logging handler that provides Rich-based colored output with consistent formatting."""
1315

14-
Returns:
15-
FastMCP logger configured with application settings.
16-
"""
17-
logger = get_logger("FastMCP")
16+
def __init__(self):
17+
super().__init__(sys.stdout)
1818

19-
logger.setLevel(getattr(logging, Config.LOG_LEVEL))
19+
self.console = Console(file=sys.stdout, force_terminal=True)
20+
self.rich_handler = RichHandler(
21+
console=self.console,
22+
show_time=True,
23+
show_level=True,
24+
show_path=False,
25+
omit_repeated_times=False,
26+
markup=False,
27+
rich_tracebacks=True,
28+
tracebacks_show_locals=False,
29+
)
30+
self.rich_handler.setFormatter(logging.Formatter("%(message)s"))
31+
32+
def emit(self, record):
33+
try:
34+
original_msg = str(record.getMessage())
35+
36+
# Parse uvicorn format: "INFO: message"
37+
uvicorn_pattern = re.match(
38+
r"^(INFO|ERROR|WARNING|DEBUG):\s*(.*)$", original_msg
39+
)
40+
if uvicorn_pattern:
41+
level = uvicorn_pattern.group(1)
42+
message = uvicorn_pattern.group(2).strip()
43+
record.msg = message
44+
record.levelname = level
45+
record.levelno = getattr(logging, level)
46+
47+
# Parse bracket format: "[timestamp] LEVEL message"
48+
elif original_msg.startswith("[") and (
49+
"] INFO" in original_msg
50+
or "] ERROR" in original_msg
51+
or "] WARNING" in original_msg
52+
or "] DEBUG" in original_msg
53+
):
54+
for level in ["INFO", "ERROR", "WARNING", "DEBUG"]:
55+
if f"] {level}" in original_msg:
56+
bracket_match = re.search(rf"\]\s*{level}\s*(.*)", original_msg)
57+
if bracket_match:
58+
record.msg = bracket_match.group(1).strip()
59+
record.levelname = level
60+
record.levelno = getattr(logging, level)
61+
break
62+
63+
# Parse format: "logger - LEVEL - message"
64+
elif " - " in original_msg and any(
65+
level in original_msg for level in ["INFO", "ERROR", "WARNING", "DEBUG"]
66+
):
67+
parts = original_msg.split(" - ", 2)
68+
if len(parts) >= 3:
69+
logger_name, level, message = parts
70+
if level in ["INFO", "ERROR", "WARNING", "DEBUG"]:
71+
record.msg = message.strip()
72+
record.levelname = level
73+
record.levelno = getattr(logging, level)
74+
75+
record.msg = str(record.msg).replace("\n", " ").strip()
76+
self.rich_handler.emit(record)
77+
78+
except Exception:
79+
super().emit(record)
80+
81+
82+
def configure_logging() -> None:
83+
"""Configure application logging using Rich for colored output."""
84+
85+
handler = RichLoggingHandler()
86+
handler.setLevel(logging.DEBUG)
2087

21-
for handler in logger.handlers:
22-
formatter = logging.Formatter(Config.LOG_FORMAT)
23-
handler.setFormatter(formatter)
24-
handler.setLevel(getattr(logging, Config.LOG_LEVEL))
88+
root_logger = logging.getLogger()
89+
root_logger.handlers.clear()
90+
root_logger.addHandler(handler)
91+
root_logger.setLevel(logging.DEBUG)
92+
93+
logger_names = [
94+
# Uvicorn
95+
"uvicorn",
96+
"uvicorn.error",
97+
"uvicorn.access",
98+
# FastMCP framework
99+
"FastMCP",
100+
"fastmcp",
101+
# MCP server
102+
"mcp.server.lowlevel.server",
103+
"mcp.server.sse",
104+
"mcp.server.fastmcp.server",
105+
"mcp.server.fastmcp.tools.tool_manager",
106+
"mcp.server.fastmcp.resources.resource_manager",
107+
"mcp.server.fastmcp.prompts.manager",
108+
# Application
109+
"mcp_server",
110+
"mcp_server.auth",
111+
"mcp_server.app",
112+
# System loggers
113+
"asyncio",
114+
"rich",
115+
"httpx",
116+
]
117+
118+
for logger_name in logger_names:
119+
logger = logging.getLogger(logger_name)
120+
logger.addHandler(handler)
121+
logger.setLevel(getattr(logging, Config.LOG_LEVEL))
122+
logger.propagate = False # Prevent double logging
123+
124+
125+
def get_app_logger(name: str = "mcp_server") -> logging.Logger:
126+
"""Get a configured logger for the application."""
127+
logger = logging.getLogger(name)
128+
129+
handler = RichLoggingHandler()
130+
handler.setLevel(getattr(logging, Config.LOG_LEVEL))
131+
132+
logger.handlers.clear()
133+
logger.addHandler(handler)
134+
logger.setLevel(getattr(logging, Config.LOG_LEVEL))
135+
logger.propagate = False
25136

26137
return logger
138+
139+
140+
configure_logging()

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies = [
1010
"pydantic>=2.11.5",
1111
"python-dotenv>=1.1.0",
1212
"fastmcp>=2.8.0",
13+
"rich>=13.0.0",
1314
]
1415

1516
[build-system]

uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)