107 lines
3.9 KiB
Python

"""
Simple logger with automatic function-level context tracking for KiCad MCP Server.
Usage examples:
# Creates logs in the "logs" directory by default
logger = Logger()
# To disable file logging completely
logger = Logger(log_dir=None)
# Or to specify a custom logs directory
logger = Logger(log_dir="custom_logs")
"""
import os
import sys
import logging
import inspect
from datetime import datetime
from pathlib import Path
class Logger:
"""
Simple logger that automatically tracks function-level context.
"""
def __init__(self, name=None, log_dir=None, console_level=logging.INFO, file_level=logging.DEBUG):
"""
Initialize a logger with automatic function-level context.
Args:
name: Logger name (defaults to calling module name)
log_dir: Directory to store log files (default: "logs" directory)
Set to None to disable file logging
console_level: Logging level for console output
file_level: Logging level for file output
"""
# If no name provided, try to determine it from the calling module
if name is None:
frame = inspect.currentframe().f_back
module = inspect.getmodule(frame)
self.name = module.__name__ if module else "kicad_mcp"
else:
self.name = name
# Initialize Python's logger
self.logger = logging.getLogger(self.name)
self.logger.setLevel(logging.DEBUG) # Capture all levels, filtering at handler level
# Only configure if not already configured
if not self.logger.handlers and not logging.getLogger().handlers:
# Create formatter with detailed context
formatter = logging.Formatter(
'%(asctime)s [%(levelname)s] %(pathname)s:%(funcName)s:%(lineno)d - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
# Set up console output
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(console_level)
console_handler.setFormatter(formatter)
self.logger.addHandler(console_handler)
# Set up file output by default unless explicitly disabled
if log_dir is not None:
log_dir_path = Path(log_dir)
log_dir_path.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
log_file = log_dir_path / f"kicad_mcp_{timestamp}.log"
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(file_level)
file_handler.setFormatter(formatter)
self.logger.addHandler(file_handler)
self.info(f"Logging session started, log file: {log_file}")
def _get_caller_info(self):
"""Get information about the function that called the logger."""
# Skip this function, the log method, and get to the actual caller
frame = inspect.currentframe().f_back.f_back
return frame
def debug(self, message):
"""Log a debug message with caller context."""
self.logger.debug(message)
def info(self, message):
"""Log an info message with caller context."""
self.logger.info(message)
def warning(self, message):
"""Log a warning message with caller context."""
self.logger.warning(message)
def error(self, message):
"""Log an error message with caller context."""
self.logger.error(message)
def critical(self, message):
"""Log a critical message with caller context."""
self.logger.critical(message)
def exception(self, message):
"""Log an exception message with caller context and traceback."""
self.logger.exception(message)