Skip to main content
Glama
logger.py3.83 kB
"""Structured logging for Pathfinder MCP Server.""" import json import logging import sys from datetime import datetime, timezone from typing import Any class JSONFormatter(logging.Formatter): """JSON formatter for structured logging.""" def format(self, record: logging.LogRecord) -> str: """Format log record as JSON.""" log_obj: dict[str, Any] = { "timestamp": datetime.now(timezone.utc).isoformat(), "level": record.levelname, "message": record.getMessage(), "logger": record.name, } # Add extra fields if present if hasattr(record, "session_id"): log_obj["session_id"] = record.session_id if hasattr(record, "phase"): log_obj["phase"] = record.phase if hasattr(record, "tool"): log_obj["tool"] = record.tool if hasattr(record, "event"): log_obj["event"] = record.event # Add exception info if present if record.exc_info: log_obj["exception"] = self.formatException(record.exc_info) return json.dumps(log_obj) def get_logger(name: str = "pathfinder") -> logging.Logger: """Get configured logger instance. Args: name: Logger name Returns: Configured logger """ logger = logging.getLogger(name) # Only configure if not already configured if not logger.handlers: logger.setLevel(logging.DEBUG) # Output to stderr (stdio transport compatible) handler = logging.StreamHandler(sys.stderr) handler.setFormatter(JSONFormatter()) logger.addHandler(handler) return logger # Module-level logger logger = get_logger() def log_phase_transition( session_id: str, from_phase: str, to_phase: str, **extra: Any ) -> None: """Log a phase transition event.""" logger.info( f"Phase transition: {from_phase} -> {to_phase}", extra={ "session_id": session_id, "phase": to_phase, "event": "phase_transition", "from_phase": from_phase, **extra, }, ) def log_tool_call(tool_name: str, session_id: str | None = None, **extra: Any) -> None: """Log a tool invocation.""" logger.debug( f"Tool called: {tool_name}", extra={ "tool": tool_name, "session_id": session_id, "event": "tool_call", **extra, }, ) def log_tool_result( tool_name: str, success: bool, session_id: str | None = None, error: str | None = None, **extra: Any, ) -> None: """Log a tool result.""" level = logging.INFO if success else logging.ERROR msg = f"Tool completed: {tool_name}" if success else f"Tool failed: {tool_name}" logger.log( level, msg, extra={ "tool": tool_name, "session_id": session_id, "event": "tool_result", "success": success, "error": error, **extra, }, ) def log_compaction( session_id: str, pre_tokens: int, post_tokens: int, **extra: Any ) -> None: """Log a context compaction event.""" logger.info( f"Context compacted: {pre_tokens} -> {post_tokens} tokens", extra={ "session_id": session_id, "event": "compaction", "pre_tokens": pre_tokens, "post_tokens": post_tokens, **extra, }, ) def log_error( message: str, session_id: str | None = None, tool: str | None = None, **extra: Any, ) -> None: """Log an error.""" logger.error( message, extra={ "session_id": session_id, "tool": tool, "event": "error", **extra, }, )

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jamesctucker/pathfinder-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server