MCP Pytest Server
by kieranlal
- src
- meilisearch_mcp
import logging
import sys
import json
from datetime import datetime
from pathlib import Path
from typing import Optional, Dict, Any
import threading
from queue import Queue
import asyncio
class AsyncLogHandler:
"""Asynchronous log handler with buffering"""
def __init__(self, max_buffer: int = 1000):
self.buffer = Queue(maxsize=max_buffer)
self.running = True
self.worker_thread = threading.Thread(target=self._worker)
self.worker_thread.daemon = True
self.worker_thread.start()
def _worker(self):
"""Background worker to process logs"""
while self.running:
try:
record = self.buffer.get(timeout=1.0)
self._write_log(record)
except:
continue
def _write_log(self, record: Dict[str, Any]):
"""Write log record to storage"""
raise NotImplementedError
def emit(self, record: Dict[str, Any]):
"""Add log record to buffer"""
try:
self.buffer.put(record, block=False)
except:
pass # Buffer full, skip log
def shutdown(self):
"""Shutdown the handler"""
self.running = False
self.worker_thread.join()
class FileLogHandler(AsyncLogHandler):
"""File-based log handler"""
def __init__(self, log_dir: str):
super().__init__()
self.log_dir = Path(log_dir)
self.log_dir.mkdir(parents=True, exist_ok=True)
self.current_file = None
self._rotate_file()
def _rotate_file(self):
"""Rotate log file daily"""
date_str = datetime.now().strftime("%Y-%m-%d")
self.current_file = self.log_dir / f"meilisearch-mcp-{date_str}.log"
def _write_log(self, record: Dict[str, Any]):
"""Write log record to file"""
current_date = datetime.now().strftime("%Y-%m-%d")
if not self.current_file or current_date not in self.current_file.name:
self._rotate_file()
with open(self.current_file, "a") as f:
f.write(json.dumps(record) + "\n")
class MCPLogger:
"""Enhanced MCP logger with structured logging"""
def __init__(self, name: str = "meilisearch-mcp", log_dir: Optional[str] = None):
self.logger = logging.getLogger(name)
self._setup_logger(log_dir)
def _setup_logger(self, log_dir: Optional[str]):
"""Configure logging with multiple handlers"""
if not self.logger.handlers:
# Console handler
console_handler = logging.StreamHandler(sys.stderr)
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
console_handler.setFormatter(formatter)
self.logger.addHandler(console_handler)
# File handler for structured logging
if log_dir:
self.file_handler = FileLogHandler(log_dir)
self.logger.setLevel(logging.INFO)
def _log(self, level: str, msg: str, **kwargs):
"""Create structured log entry"""
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"level": level,
"message": msg,
**kwargs,
}
# Log to console
getattr(self.logger, level.lower())(msg)
# Log structured data to file
if hasattr(self, "file_handler"):
self.file_handler.emit(log_entry)
def debug(self, msg: str, **kwargs):
self._log("DEBUG", msg, **kwargs)
def info(self, msg: str, **kwargs):
self._log("INFO", msg, **kwargs)
def warning(self, msg: str, **kwargs):
self._log("WARNING", msg, **kwargs)
def error(self, msg: str, **kwargs):
self._log("ERROR", msg, **kwargs)
def shutdown(self):
"""Clean shutdown of logger"""
if hasattr(self, "file_handler"):
self.file_handler.shutdown()