We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/wshobson/maverick-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""
Structured logging configuration settings for the backtesting system.
This module provides centralized configuration for all logging-related settings
including debug mode, log levels, output formats, and performance monitoring.
"""
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any
@dataclass
class LoggingSettings:
"""Comprehensive logging configuration settings."""
# Basic logging configuration
log_level: str = "INFO"
log_format: str = "json" # json or text
enable_async_logging: bool = True
console_output: str = "stderr" # stdout or stderr
# File logging configuration
enable_file_logging: bool = True
log_file_path: str = "logs/backtesting.log"
enable_log_rotation: bool = True
max_log_size_mb: int = 10
backup_count: int = 5
# Debug mode configuration
debug_enabled: bool = False
verbose_modules: list[str] = None
log_request_response: bool = False
max_payload_length: int = 1000
# Performance monitoring
enable_performance_logging: bool = True
performance_log_threshold_ms: float = 1000.0
enable_resource_tracking: bool = True
enable_business_metrics: bool = True
# Async logging configuration
async_log_queue_size: int = 10000
async_log_flush_interval: float = 1.0
# Sensitive data handling
mask_sensitive_data: bool = True
sensitive_field_patterns: list[str] = None
# Remote logging (for future log aggregation)
enable_remote_logging: bool = False
remote_endpoint: str | None = None
remote_api_key: str | None = None
# Correlation and tracing
enable_correlation_tracking: bool = True
correlation_id_header: str = "X-Correlation-ID"
enable_request_tracing: bool = True
def __post_init__(self):
"""Initialize default values for mutable fields."""
if self.verbose_modules is None:
self.verbose_modules = []
if self.sensitive_field_patterns is None:
self.sensitive_field_patterns = [
"password",
"token",
"key",
"secret",
"auth",
"credential",
"bearer",
"session",
"cookie",
"api_key",
"access_token",
"refresh_token",
"private",
"confidential",
]
@classmethod
def from_env(cls) -> "LoggingSettings":
"""Create logging settings from environment variables."""
return cls(
log_level=os.getenv("MAVERICK_LOG_LEVEL", "INFO").upper(),
log_format=os.getenv("MAVERICK_LOG_FORMAT", "json").lower(),
enable_async_logging=os.getenv("MAVERICK_ASYNC_LOGGING", "true").lower()
== "true",
console_output=os.getenv("MAVERICK_CONSOLE_OUTPUT", "stderr").lower(),
# File logging
enable_file_logging=os.getenv("MAVERICK_FILE_LOGGING", "true").lower()
== "true",
log_file_path=os.getenv("MAVERICK_LOG_FILE", "logs/backtesting.log"),
enable_log_rotation=os.getenv("MAVERICK_LOG_ROTATION", "true").lower()
== "true",
max_log_size_mb=int(os.getenv("MAVERICK_LOG_SIZE_MB", "10")),
backup_count=int(os.getenv("MAVERICK_LOG_BACKUPS", "5")),
# Debug configuration
debug_enabled=os.getenv("MAVERICK_DEBUG", "false").lower() == "true",
log_request_response=os.getenv("MAVERICK_LOG_REQUESTS", "false").lower()
== "true",
max_payload_length=int(os.getenv("MAVERICK_MAX_PAYLOAD", "1000")),
# Performance monitoring
enable_performance_logging=os.getenv(
"MAVERICK_PERF_LOGGING", "true"
).lower()
== "true",
performance_log_threshold_ms=float(
os.getenv("MAVERICK_PERF_THRESHOLD", "1000.0")
),
enable_resource_tracking=os.getenv(
"MAVERICK_RESOURCE_TRACKING", "true"
).lower()
== "true",
enable_business_metrics=os.getenv(
"MAVERICK_BUSINESS_METRICS", "true"
).lower()
== "true",
# Async logging
async_log_queue_size=int(os.getenv("MAVERICK_LOG_QUEUE_SIZE", "10000")),
async_log_flush_interval=float(
os.getenv("MAVERICK_LOG_FLUSH_INTERVAL", "1.0")
),
# Sensitive data
mask_sensitive_data=os.getenv("MAVERICK_MASK_SENSITIVE", "true").lower()
== "true",
# Remote logging
enable_remote_logging=os.getenv("MAVERICK_REMOTE_LOGGING", "false").lower()
== "true",
remote_endpoint=os.getenv("MAVERICK_REMOTE_LOG_ENDPOINT"),
remote_api_key=os.getenv("MAVERICK_REMOTE_LOG_API_KEY"),
# Correlation and tracing
enable_correlation_tracking=os.getenv(
"MAVERICK_CORRELATION_TRACKING", "true"
).lower()
== "true",
correlation_id_header=os.getenv(
"MAVERICK_CORRELATION_HEADER", "X-Correlation-ID"
),
enable_request_tracing=os.getenv("MAVERICK_REQUEST_TRACING", "true").lower()
== "true",
)
def to_dict(self) -> dict[str, Any]:
"""Convert settings to dictionary for serialization."""
return {
"log_level": self.log_level,
"log_format": self.log_format,
"enable_async_logging": self.enable_async_logging,
"console_output": self.console_output,
"enable_file_logging": self.enable_file_logging,
"log_file_path": self.log_file_path,
"enable_log_rotation": self.enable_log_rotation,
"max_log_size_mb": self.max_log_size_mb,
"backup_count": self.backup_count,
"debug_enabled": self.debug_enabled,
"verbose_modules": self.verbose_modules,
"log_request_response": self.log_request_response,
"max_payload_length": self.max_payload_length,
"enable_performance_logging": self.enable_performance_logging,
"performance_log_threshold_ms": self.performance_log_threshold_ms,
"enable_resource_tracking": self.enable_resource_tracking,
"enable_business_metrics": self.enable_business_metrics,
"async_log_queue_size": self.async_log_queue_size,
"async_log_flush_interval": self.async_log_flush_interval,
"mask_sensitive_data": self.mask_sensitive_data,
"sensitive_field_patterns": self.sensitive_field_patterns,
"enable_remote_logging": self.enable_remote_logging,
"remote_endpoint": self.remote_endpoint,
"enable_correlation_tracking": self.enable_correlation_tracking,
"correlation_id_header": self.correlation_id_header,
"enable_request_tracing": self.enable_request_tracing,
}
def ensure_log_directory(self):
"""Ensure the log directory exists."""
if self.enable_file_logging and self.log_file_path:
log_path = Path(self.log_file_path)
log_path.parent.mkdir(parents=True, exist_ok=True)
def get_debug_modules(self) -> list[str]:
"""Get list of modules for debug logging."""
if not self.debug_enabled:
return []
if not self.verbose_modules:
# Default debug modules for backtesting
return [
"maverick_mcp.backtesting",
"maverick_mcp.api.tools.backtesting",
"maverick_mcp.providers",
"maverick_mcp.data.cache",
]
return self.verbose_modules
def should_log_performance(self, duration_ms: float) -> bool:
"""Check if operation should be logged based on performance threshold."""
if not self.enable_performance_logging:
return False
return duration_ms >= self.performance_log_threshold_ms
def get_log_file_config(self) -> dict[str, Any]:
"""Get file logging configuration."""
if not self.enable_file_logging:
return {}
config = {
"filename": self.log_file_path,
"mode": "a",
"encoding": "utf-8",
}
if self.enable_log_rotation:
config.update(
{
"maxBytes": self.max_log_size_mb * 1024 * 1024,
"backupCount": self.backup_count,
}
)
return config
def get_performance_config(self) -> dict[str, Any]:
"""Get performance monitoring configuration."""
return {
"enabled": self.enable_performance_logging,
"threshold_ms": self.performance_log_threshold_ms,
"resource_tracking": self.enable_resource_tracking,
"business_metrics": self.enable_business_metrics,
}
def get_debug_config(self) -> dict[str, Any]:
"""Get debug configuration."""
return {
"enabled": self.debug_enabled,
"verbose_modules": self.get_debug_modules(),
"log_request_response": self.log_request_response,
"max_payload_length": self.max_payload_length,
}
# Environment-specific configurations
class EnvironmentLogSettings:
"""Environment-specific logging configurations."""
@staticmethod
def development() -> LoggingSettings:
"""Development environment logging configuration."""
return LoggingSettings(
log_level="DEBUG",
log_format="text",
debug_enabled=True,
log_request_response=True,
enable_performance_logging=True,
performance_log_threshold_ms=100.0, # Lower threshold for development
console_output="stdout",
enable_file_logging=True,
log_file_path="logs/dev_backtesting.log",
)
@staticmethod
def testing() -> LoggingSettings:
"""Testing environment logging configuration."""
return LoggingSettings(
log_level="WARNING",
log_format="text",
debug_enabled=False,
enable_performance_logging=False,
enable_file_logging=False,
console_output="stdout",
enable_async_logging=False, # Synchronous for tests
)
@staticmethod
def production() -> LoggingSettings:
"""Production environment logging configuration."""
return LoggingSettings(
log_level="INFO",
log_format="json",
debug_enabled=False,
log_request_response=False,
enable_performance_logging=True,
performance_log_threshold_ms=2000.0, # Higher threshold for production
console_output="stderr",
enable_file_logging=True,
log_file_path="/var/log/maverick/backtesting.log",
enable_log_rotation=True,
max_log_size_mb=50, # Larger files in production
backup_count=10,
enable_remote_logging=True, # Enable for log aggregation
)
# Global logging settings instance
_logging_settings: LoggingSettings | None = None
def get_logging_settings() -> LoggingSettings:
"""Get global logging settings instance."""
global _logging_settings
if _logging_settings is None:
environment = os.getenv("MAVERICK_ENVIRONMENT", "development").lower()
if environment == "development":
_logging_settings = EnvironmentLogSettings.development()
elif environment == "testing":
_logging_settings = EnvironmentLogSettings.testing()
elif environment == "production":
_logging_settings = EnvironmentLogSettings.production()
else:
# Default to environment variables
_logging_settings = LoggingSettings.from_env()
# Override with any environment variables
env_overrides = LoggingSettings.from_env()
for key, value in env_overrides.to_dict().items():
if value is not None and value != getattr(LoggingSettings(), key):
setattr(_logging_settings, key, value)
# Ensure log directory exists
_logging_settings.ensure_log_directory()
return _logging_settings
def configure_logging_for_environment(environment: str) -> LoggingSettings:
"""Configure logging for specific environment."""
global _logging_settings
if environment.lower() == "development":
_logging_settings = EnvironmentLogSettings.development()
elif environment.lower() == "testing":
_logging_settings = EnvironmentLogSettings.testing()
elif environment.lower() == "production":
_logging_settings = EnvironmentLogSettings.production()
else:
raise ValueError(f"Unknown environment: {environment}")
_logging_settings.ensure_log_directory()
return _logging_settings
# Logging configuration validation
def validate_logging_settings(settings: LoggingSettings) -> list[str]:
"""Validate logging settings and return list of warnings/errors."""
warnings = []
# Validate log level
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
if settings.log_level not in valid_levels:
warnings.append(f"Invalid log level '{settings.log_level}', using INFO")
# Validate log format
valid_formats = ["json", "text"]
if settings.log_format not in valid_formats:
warnings.append(f"Invalid log format '{settings.log_format}', using json")
# Validate console output
valid_outputs = ["stdout", "stderr"]
if settings.console_output not in valid_outputs:
warnings.append(
f"Invalid console output '{settings.console_output}', using stderr"
)
# Validate file logging
if settings.enable_file_logging:
try:
log_path = Path(settings.log_file_path)
log_path.parent.mkdir(parents=True, exist_ok=True)
except Exception as e:
warnings.append(f"Cannot create log directory: {e}")
# Validate performance settings
if settings.performance_log_threshold_ms < 0:
warnings.append("Performance threshold cannot be negative, using 1000ms")
# Validate async settings
if settings.async_log_queue_size < 100:
warnings.append("Async log queue size too small, using 1000")
return warnings