feat: initial commit from workspace-mcp
Some checks failed
Check Maintainer Edits Enabled / check-maintainer-edits (pull_request) Has been cancelled
Check Maintainer Edits Enabled / check-maintainer-edits-internal (pull_request) Has been cancelled
Docker Build and Push to GHCR / build-and-push (pull_request) Has been cancelled
Ruff / ruff (pull_request) Has been cancelled
Some checks failed
Check Maintainer Edits Enabled / check-maintainer-edits (pull_request) Has been cancelled
Check Maintainer Edits Enabled / check-maintainer-edits-internal (pull_request) Has been cancelled
Docker Build and Push to GHCR / build-and-push (pull_request) Has been cancelled
Ruff / ruff (pull_request) Has been cancelled
This commit is contained in:
207
core/log_formatter.py
Normal file
207
core/log_formatter.py
Normal file
@@ -0,0 +1,207 @@
|
||||
"""
|
||||
Enhanced Log Formatter for Google Workspace MCP
|
||||
|
||||
Provides visually appealing log formatting with emojis and consistent styling
|
||||
to match the safe_print output format.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class EnhancedLogFormatter(logging.Formatter):
|
||||
"""Custom log formatter that adds ASCII prefixes and visual enhancements to log messages."""
|
||||
|
||||
# Color codes for terminals that support ANSI colors
|
||||
COLORS = {
|
||||
"DEBUG": "\033[36m", # Cyan
|
||||
"INFO": "\033[32m", # Green
|
||||
"WARNING": "\033[33m", # Yellow
|
||||
"ERROR": "\033[31m", # Red
|
||||
"CRITICAL": "\033[35m", # Magenta
|
||||
"RESET": "\033[0m", # Reset
|
||||
}
|
||||
|
||||
def __init__(self, use_colors: bool = True, *args, **kwargs):
|
||||
"""
|
||||
Initialize the emoji log formatter.
|
||||
|
||||
Args:
|
||||
use_colors: Whether to use ANSI color codes (default: True)
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.use_colors = use_colors
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
"""Format the log record with ASCII prefixes and enhanced styling."""
|
||||
# Get the appropriate ASCII prefix for the service
|
||||
service_prefix = self._get_ascii_prefix(record.name, record.levelname)
|
||||
|
||||
# Format the message with enhanced styling
|
||||
formatted_msg = self._enhance_message(record.getMessage())
|
||||
|
||||
# Build the formatted log entry
|
||||
if self.use_colors:
|
||||
color = self.COLORS.get(record.levelname, "")
|
||||
reset = self.COLORS["RESET"]
|
||||
return f"{service_prefix} {color}{formatted_msg}{reset}"
|
||||
else:
|
||||
return f"{service_prefix} {formatted_msg}"
|
||||
|
||||
def _get_ascii_prefix(self, logger_name: str, level_name: str) -> str:
|
||||
"""Get ASCII-safe prefix for Windows compatibility."""
|
||||
# ASCII-safe prefixes for different services
|
||||
ascii_prefixes = {
|
||||
"core.tool_tier_loader": "[TOOLS]",
|
||||
"core.tool_registry": "[REGISTRY]",
|
||||
"auth.scopes": "[AUTH]",
|
||||
"core.utils": "[UTILS]",
|
||||
"auth.google_auth": "[OAUTH]",
|
||||
"auth.credential_store": "[CREDS]",
|
||||
"gcalendar.calendar_tools": "[CALENDAR]",
|
||||
"gdrive.drive_tools": "[DRIVE]",
|
||||
"gmail.gmail_tools": "[GMAIL]",
|
||||
"gdocs.docs_tools": "[DOCS]",
|
||||
"gsheets.sheets_tools": "[SHEETS]",
|
||||
"gchat.chat_tools": "[CHAT]",
|
||||
"gforms.forms_tools": "[FORMS]",
|
||||
"gslides.slides_tools": "[SLIDES]",
|
||||
"gtasks.tasks_tools": "[TASKS]",
|
||||
"gsearch.search_tools": "[SEARCH]",
|
||||
}
|
||||
|
||||
return ascii_prefixes.get(logger_name, f"[{level_name}]")
|
||||
|
||||
def _enhance_message(self, message: str) -> str:
|
||||
"""Enhance the log message with better formatting."""
|
||||
# Handle common patterns for better visual appeal
|
||||
|
||||
# Tool tier loading messages
|
||||
if "resolved to" in message and "tools across" in message:
|
||||
# Extract numbers and service names for better formatting
|
||||
pattern = (
|
||||
r"Tier '(\w+)' resolved to (\d+) tools across (\d+) services: (.+)"
|
||||
)
|
||||
match = re.search(pattern, message)
|
||||
if match:
|
||||
tier, tool_count, service_count, services = match.groups()
|
||||
return f"Tool tier '{tier}' loaded: {tool_count} tools across {service_count} services [{services}]"
|
||||
|
||||
# Configuration loading messages
|
||||
if "Loaded tool tiers configuration from" in message:
|
||||
path = message.split("from ")[-1]
|
||||
return f"Configuration loaded from {path}"
|
||||
|
||||
# Tool filtering messages
|
||||
if "Tool tier filtering" in message:
|
||||
pattern = r"removed (\d+) tools, (\d+) enabled"
|
||||
match = re.search(pattern, message)
|
||||
if match:
|
||||
removed, enabled = match.groups()
|
||||
return f"Tool filtering complete: {enabled} tools enabled ({removed} filtered out)"
|
||||
|
||||
# Enabled tools messages
|
||||
if "Enabled tools set for scope management" in message:
|
||||
tools = message.split(": ")[-1]
|
||||
return f"Scope management configured for tools: {tools}"
|
||||
|
||||
# Credentials directory messages
|
||||
if "Credentials directory permissions check passed" in message:
|
||||
path = message.split(": ")[-1]
|
||||
return f"Credentials directory verified: {path}"
|
||||
|
||||
# If no specific pattern matches, return the original message
|
||||
return message
|
||||
|
||||
|
||||
def setup_enhanced_logging(
|
||||
log_level: int = logging.INFO, use_colors: bool = True
|
||||
) -> None:
|
||||
"""
|
||||
Set up enhanced logging with ASCII prefix formatter for the entire application.
|
||||
|
||||
Args:
|
||||
log_level: The logging level to use (default: INFO)
|
||||
use_colors: Whether to use ANSI colors (default: True)
|
||||
"""
|
||||
# Create the enhanced formatter
|
||||
formatter = EnhancedLogFormatter(use_colors=use_colors)
|
||||
|
||||
# Get the root logger
|
||||
root_logger = logging.getLogger()
|
||||
|
||||
# Update existing console handlers
|
||||
for handler in root_logger.handlers:
|
||||
if isinstance(handler, logging.StreamHandler) and handler.stream.name in [
|
||||
"<stderr>",
|
||||
"<stdout>",
|
||||
]:
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
# If no console handler exists, create one
|
||||
console_handlers = [
|
||||
h
|
||||
for h in root_logger.handlers
|
||||
if isinstance(h, logging.StreamHandler)
|
||||
and h.stream.name in ["<stderr>", "<stdout>"]
|
||||
]
|
||||
|
||||
if not console_handlers:
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.setLevel(log_level)
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
|
||||
def configure_file_logging(logger_name: str = None) -> bool:
|
||||
"""
|
||||
Configure file logging based on stateless mode setting.
|
||||
|
||||
In stateless mode, file logging is completely disabled to avoid filesystem writes.
|
||||
In normal mode, sets up detailed file logging to 'mcp_server_debug.log'.
|
||||
|
||||
Args:
|
||||
logger_name: Optional name for the logger (defaults to root logger)
|
||||
|
||||
Returns:
|
||||
bool: True if file logging was configured, False if skipped (stateless mode)
|
||||
"""
|
||||
# Check if stateless mode is enabled
|
||||
stateless_mode = (
|
||||
os.getenv("WORKSPACE_MCP_STATELESS_MODE", "false").lower() == "true"
|
||||
)
|
||||
|
||||
if stateless_mode:
|
||||
logger = logging.getLogger(logger_name)
|
||||
logger.debug("File logging disabled in stateless mode")
|
||||
return False
|
||||
|
||||
# Configure file logging for normal mode
|
||||
try:
|
||||
target_logger = logging.getLogger(logger_name)
|
||||
log_file_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# Go up one level since we're in core/ subdirectory
|
||||
log_file_dir = os.path.dirname(log_file_dir)
|
||||
log_file_path = os.path.join(log_file_dir, "mcp_server_debug.log")
|
||||
|
||||
file_handler = logging.FileHandler(log_file_path, mode="a")
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
|
||||
file_formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(process)d - %(threadName)s "
|
||||
"[%(module)s.%(funcName)s:%(lineno)d] - %(message)s"
|
||||
)
|
||||
file_handler.setFormatter(file_formatter)
|
||||
target_logger.addHandler(file_handler)
|
||||
|
||||
logger = logging.getLogger(logger_name)
|
||||
logger.debug(f"Detailed file logging configured to: {log_file_path}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write(
|
||||
f"CRITICAL: Failed to set up file logging to '{log_file_path}': {e}\n"
|
||||
)
|
||||
return False
|
||||
Reference in New Issue
Block a user