"""
Logging configuration with JSON output support.
Configures structured logging for production use.
"""
from __future__ import annotations
import logging
import sys
from typing import Any
from .config import settings
class JSONFormatter(logging.Formatter):
"""
JSON log formatter for structured logging.
Outputs logs in JSON format for easy parsing by log aggregators.
"""
def format(self, record: logging.LogRecord) -> str:
import json
from datetime import datetime, timezone
log_data: dict[str, Any] = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"function": record.funcName,
"line": record.lineno,
}
# Add extra fields if present
if hasattr(record, "details") and record.details:
log_data["details"] = record.details
# Add exception info if present
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
return json.dumps(log_data, default=str)
class TextFormatter(logging.Formatter):
"""
Text log formatter for development.
Outputs human-readable logs with 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",
}
def format(self, record: logging.LogRecord) -> str:
color = self.COLORS.get(record.levelname, "")
reset = self.COLORS["RESET"]
timestamp = self.formatTime(record, "%Y-%m-%d %H:%M:%S")
message = record.getMessage()
formatted = f"{timestamp} {color}{record.levelname:8}{reset} [{record.name}] {message}"
if record.exc_info:
formatted += "\n" + self.formatException(record.exc_info)
return formatted
def setup_logging() -> None:
"""
Configure logging based on settings.
Sets up JSON or text formatter based on LOG_FORMAT environment variable.
"""
log_level = getattr(logging, settings.logging.level, logging.INFO)
# Create handler
handler = logging.StreamHandler(sys.stderr)
# Set formatter based on config
if settings.logging.format == "json":
handler.setFormatter(JSONFormatter())
else:
handler.setFormatter(TextFormatter())
# Configure root logger
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
root_logger.handlers.clear()
root_logger.addHandler(handler)
# Configure package logger
package_logger = logging.getLogger("mcp_odoo")
package_logger.setLevel(log_level)
# Reduce noise from external libraries
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("xmlrpc").setLevel(logging.WARNING)
def get_logger(name: str) -> logging.Logger:
"""
Get a logger instance.
Args:
name: Logger name (usually __name__)
Returns:
Configured logger instance
"""
return logging.getLogger(name)