11"""
22Central logging configuration for MCP as a Judge.
33
4- This module provides centralized logging setup with colored output and
5- consistent formatting across the entire application .
4+ This module provides centralized logging setup with MCP Context integration
5+ when available, falling back to standard logging otherwise .
66"""
77
88import logging
99import sys
1010from datetime import datetime
11- from typing import Any , ClassVar
11+ from typing import Any
1212
13+ # Import MCP SDK logging utilities for proper color support
14+ try :
15+ from mcp .server .fastmcp .utilities .logging import (
16+ configure_logging ,
17+ )
18+ from mcp .server .fastmcp .utilities .logging import (
19+ get_logger as mcp_get_logger ,
20+ )
1321
14- class ColoredFormatter (logging .Formatter ):
15- """Custom formatter that adds colors to log levels and formats messages."""
22+ MCP_SDK_AVAILABLE = True
23+ except ImportError :
24+ configure_logging = None # type: ignore[assignment]
25+ mcp_get_logger = None # type: ignore[assignment]
26+ MCP_SDK_AVAILABLE = False
1627
17- # ANSI color codes
18- COLORS : ClassVar [dict [str , str ]] = {
19- "DEBUG" : "\033 [36m" , # Cyan
20- "INFO" : "\033 [32m" , # Green
21- "WARNING" : "\033 [33m" , # Yellow
22- "ERROR" : "\033 [31m" , # Red
23- "CRITICAL" : "\033 [35m" , # Magenta
24- }
25- RESET = "\033 [0m" # Reset color
28+ # Global context reference for MCP integration
29+ _global_context_ref : Any | None = None
2630
27- def format (self , record : logging .LogRecord ) -> str :
28- """Format log record with colors and custom format."""
29- # Get color for log level
30- level_color = self .COLORS .get (record .levelname , "" )
3131
32- # Format: [level_with_color] [module:linenumber] [iso-date] [message]
33- colored_level = f"{ level_color } { record .levelname } { self .RESET } "
32+ def set_context_reference (ctx : Any ) -> None :
33+ """Set global context reference for MCP integration."""
34+ global _global_context_ref
35+ _global_context_ref = ctx
36+
3437
35- # Get module name (remove package prefix for cleaner output)
38+ class CleanFormatter (logging .Formatter ):
39+ """Clean formatter without ANSI colors - uses MCP SDK logging for proper color support."""
40+
41+ def format (self , record : logging .LogRecord ) -> str :
42+ """Format log record with clean output."""
43+ # Get module name from the actual caller (remove package prefix for cleaner output)
3644 module_name = record .name
3745 if module_name .startswith ("mcp_as_a_judge." ):
3846 module_name = module_name [len ("mcp_as_a_judge." ) :]
3947
4048 # Format timestamp as ISO date
4149 timestamp = datetime .fromtimestamp (record .created ).isoformat ()
4250
43- # Build the formatted message
44- formatted_message = f"[{ colored_level } ] [{ module_name } :{ record .lineno } ] [{ timestamp } ] { record .getMessage ()} "
51+ # Clean format without ANSI codes - let MCP SDK handle colors
52+ formatted_message = f"[{ record . levelname } ] [{ module_name } :{ record .lineno } ] [{ timestamp } ] { record .getMessage ()} "
4553
4654 # Handle exceptions
4755 if record .exc_info :
@@ -50,32 +58,34 @@ def format(self, record: logging.LogRecord) -> str:
5058 return formatted_message
5159
5260
53- def setup_logging (level : int = logging . INFO ) -> None :
61+ def setup_logging (level : str = " INFO" ) -> None :
5462 """
55- Set up centralized logging configuration for the entire application .
63+ Set up centralized logging configuration using MCP SDK .
5664
5765 Args:
58- level: Logging level (default: INFO)
66+ level: Logging level (default: " INFO" )
5967 """
60- # Create custom formatter
61- formatter = ColoredFormatter ()
62-
63- # Create handler for stderr (so it's visible in development tools like Cursor)
64- handler = logging .StreamHandler (sys .stderr )
65- handler .setFormatter (formatter )
68+ if MCP_SDK_AVAILABLE and configure_logging is not None :
69+ # Use MCP SDK configure_logging for proper color support
70+ configure_logging (level ) # type: ignore[misc]
71+ else :
72+ # Fallback to standard logging setup
73+ # Create custom formatter
74+ formatter = CleanFormatter ()
6675
67- # Configure root logger
68- root_logger = logging .getLogger ( )
69- root_logger . setLevel ( level )
76+ # Create handler for stderr (so it's visible in development tools like Cursor)
77+ handler = logging .StreamHandler ( sys . stderr )
78+ handler . setFormatter ( formatter )
7079
71- # Clear any existing handlers to avoid duplicates
72- root_logger .handlers .clear ()
80+ # Configure root logger
81+ root_logger = logging .getLogger ()
82+ root_logger .setLevel (getattr (logging , level .upper (), logging .INFO ))
7383
74- # Add our custom handler
75- root_logger .addHandler ( handler )
84+ # Clear any existing handlers to avoid duplicates
85+ root_logger .handlers . clear ( )
7686
77- # Configure specific loggers for our application
78- configure_application_loggers ( level )
87+ # Add our custom handler
88+ root_logger . addHandler ( handler )
7989
8090
8191def configure_application_loggers (level : int = logging .INFO ) -> None :
@@ -105,6 +115,87 @@ def configure_application_loggers(level: int = logging.INFO) -> None:
105115 logger .setLevel (level )
106116
107117
118+ class ContextAwareLogger :
119+ """Logger that automatically uses MCP Context when available."""
120+
121+ def __init__ (self , name : str ):
122+ """Initialize logger with a name."""
123+ self .name = name
124+ # Use MCP SDK logger for fallback (proper color support)
125+ if MCP_SDK_AVAILABLE :
126+ # Clean name for MCP SDK
127+ clean_name = name
128+ if name .startswith ("mcp_as_a_judge." ):
129+ clean_name = name [len ("mcp_as_a_judge." ) :]
130+ elif name == "__main__" :
131+ clean_name = "server"
132+ self ._fallback_logger = mcp_get_logger (clean_name )
133+ else :
134+ self ._fallback_logger = logging .getLogger (name )
135+
136+ async def info (self , message : str ) -> None :
137+ """Log info message using Context if available, MCP SDK logging otherwise."""
138+ global _global_context_ref
139+ if _global_context_ref is not None :
140+ await _global_context_ref .info (message )
141+ else :
142+ self ._fallback_logger .info (message )
143+
144+ async def debug (self , message : str ) -> None :
145+ """Log debug message using Context if available, MCP SDK logging otherwise."""
146+ global _global_context_ref
147+ if _global_context_ref is not None :
148+ await _global_context_ref .debug (message )
149+ else :
150+ self ._fallback_logger .debug (message )
151+
152+ async def warning (self , message : str ) -> None :
153+ """Log warning message using Context if available, MCP SDK logging otherwise."""
154+ global _global_context_ref
155+ if _global_context_ref is not None :
156+ await _global_context_ref .warning (message )
157+ else :
158+ self ._fallback_logger .warning (message )
159+
160+ async def error (self , message : str ) -> None :
161+ """Log error message using Context if available, MCP SDK logging otherwise."""
162+ global _global_context_ref
163+ if _global_context_ref is not None :
164+ await _global_context_ref .error (message )
165+ else :
166+ self ._fallback_logger .error (message )
167+
168+ # Synchronous methods for backward compatibility
169+ def info_sync (self , message : str ) -> None :
170+ """Synchronous info logging using MCP SDK."""
171+ self ._fallback_logger .info (message )
172+
173+ def debug_sync (self , message : str ) -> None :
174+ """Synchronous debug logging using MCP SDK."""
175+ self ._fallback_logger .debug (message )
176+
177+ def warning_sync (self , message : str ) -> None :
178+ """Synchronous warning logging using MCP SDK."""
179+ self ._fallback_logger .warning (message )
180+
181+ def error_sync (self , message : str ) -> None :
182+ """Synchronous error logging using MCP SDK."""
183+ self ._fallback_logger .error (message )
184+
185+
186+ def get_context_aware_logger (name : str ) -> ContextAwareLogger :
187+ """
188+ Get a context-aware logger that automatically uses MCP Context when available.
189+
190+ Args:
191+ name: Logger name (typically __name__ from the calling module)
192+
193+ Returns:
194+ ContextAwareLogger instance
195+ """
196+ return ContextAwareLogger (name )
197+
198+
108199def get_logger (name : str ) -> logging .Logger :
109200 """
110201 Get a logger instance with the given name.
0 commit comments